mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
docs: add mock API project examples (#12244)
This commit is contained in:
parent
d744a87aee
commit
a6fecb5d4b
22
examples/mock-battery/demo-battery-api/LICENSE
Normal file
22
examples/mock-battery/demo-battery-api/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Guille Paz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
25
examples/mock-battery/demo-battery-api/README.md
Normal file
25
examples/mock-battery/demo-battery-api/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Battery Status API
|
||||
|
||||
> Battery Status API Demo
|
||||
|
||||
## Demo
|
||||
http://pazguille.github.io/demo-battery-api/
|
||||
|
||||
## Support
|
||||
- Chrome 38+
|
||||
- Chrome for Android
|
||||
- Firefox 31+
|
||||
|
||||
## Specs
|
||||
http://www.w3.org/TR/battery-status
|
||||
|
||||
## Maintained by
|
||||
- Guille Paz (Frontender & Web standards lover)
|
||||
- E-mail: [guille87paz@gmail.com](mailto:guille87paz@gmail.com)
|
||||
- Twitter: [@pazguille](http://twitter.com/pazguille)
|
||||
- Web: [http://pazguille.me](http://pazguille.me)
|
||||
|
||||
## License
|
||||
Licensed under the MIT license.
|
||||
|
||||
Copyright (c) 2014 [@pazguille](http://twitter.com/pazguille).
|
51
examples/mock-battery/demo-battery-api/index.html
Normal file
51
examples/mock-battery/demo-battery-api/index.html
Normal file
@ -0,0 +1,51 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Battery Status API - Demo</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<meta http-equiv="cleartype" content="on">
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<link rel="stylesheet" href="src/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="demo-header">
|
||||
<h1 class="demo-title">Battery Status API</h1>
|
||||
<p class="not-support" hidden>Your browser doesn't support the Battery Status API :(</p>
|
||||
</header>
|
||||
|
||||
<article class="battery-card">
|
||||
<h1 class="battery-title">Battery Status</h1>
|
||||
|
||||
<div class="battery-box">
|
||||
<strong class="battery-percentage"></strong>
|
||||
<i class="battery"></i>
|
||||
</div>
|
||||
|
||||
<dl class="battery-info">
|
||||
<dt>Power Source</dt>
|
||||
<dd class="battery-status">---</dd>
|
||||
|
||||
<dt>Level percentage</dt>
|
||||
<dd class="battery-level">---</dd>
|
||||
|
||||
<dt>Fully charged in</dt>
|
||||
<dd class="battery-fully">---</dd>
|
||||
|
||||
<dt>Remaining time</dt>
|
||||
<dd class="battery-remaining">---</dd>
|
||||
</dl>
|
||||
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
<a href="https://github.com/pazguille/demo-battery-api" id="github-ribbon"><img width="149" height="149" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
|
||||
by <a href="http://pazguille.me/">Guille Paz</a> with <span class="heart">❤</span>
|
||||
<iframe id="github-button" src="https://ghbtns.com/github-btn.html?user=pazguille&repo=demo-battery-api&type=watch&count=true" allowtransparency="true" frameborder="0" scrolling="0" width="152" height="30"></iframe>
|
||||
</footer>
|
||||
|
||||
<script src="src/index.js" async></script>
|
||||
</body>
|
||||
</html>
|
BIN
examples/mock-battery/demo-battery-api/src/bolt.png
Normal file
BIN
examples/mock-battery/demo-battery-api/src/bolt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 B |
71
examples/mock-battery/demo-battery-api/src/index.js
Normal file
71
examples/mock-battery/demo-battery-api/src/index.js
Normal file
@ -0,0 +1,71 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var battery;
|
||||
|
||||
function toTime(sec) {
|
||||
sec = parseInt(sec, 10);
|
||||
|
||||
var hours = Math.floor(sec / 3600),
|
||||
minutes = Math.floor((sec - (hours * 3600)) / 60),
|
||||
seconds = sec - (hours * 3600) - (minutes * 60);
|
||||
|
||||
if (hours < 10) { hours = '0' + hours; }
|
||||
if (minutes < 10) { minutes = '0' + minutes; }
|
||||
if (seconds < 10) { seconds = '0' + seconds; }
|
||||
|
||||
return hours + ':' + minutes;
|
||||
}
|
||||
|
||||
function readBattery(b) {
|
||||
battery = b || battery;
|
||||
|
||||
var percentage = parseFloat((battery.level * 100).toFixed(2)) + '%',
|
||||
fully,
|
||||
remaining;
|
||||
|
||||
if (battery.charging && battery.chargingTime === Infinity) {
|
||||
fully = 'Calculating...';
|
||||
} else if (battery.chargingTime !== Infinity) {
|
||||
fully = toTime(battery.chargingTime);
|
||||
} else {
|
||||
fully = '---';
|
||||
}
|
||||
|
||||
if (!battery.charging && battery.dischargingTime === Infinity) {
|
||||
remaining = 'Calculating...';
|
||||
} else if (battery.dischargingTime !== Infinity) {
|
||||
remaining = toTime(battery.dischargingTime);
|
||||
} else {
|
||||
remaining = '---';
|
||||
}
|
||||
|
||||
document.styleSheets[0].insertRule('.battery:before{width:' + percentage + '}', 0);
|
||||
document.querySelector('.battery-percentage').innerHTML = percentage;
|
||||
document.querySelector('.battery-status').innerHTML = battery.charging ? 'Adapter' : 'Battery';
|
||||
document.querySelector('.battery-level').innerHTML = percentage;
|
||||
document.querySelector('.battery-fully').innerHTML = fully;
|
||||
document.querySelector('.battery-remaining').innerHTML = remaining;
|
||||
|
||||
}
|
||||
|
||||
if (navigator.battery) {
|
||||
readBattery(navigator.battery);
|
||||
|
||||
} else if (navigator.getBattery) {
|
||||
navigator.getBattery().then(readBattery);
|
||||
|
||||
} else {
|
||||
document.querySelector('.not-support').removeAttribute('hidden');
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
battery.addEventListener('chargingchange', function() {
|
||||
readBattery();
|
||||
});
|
||||
|
||||
battery.addEventListener("levelchange", function() {
|
||||
readBattery();
|
||||
});
|
||||
};
|
||||
}());
|
156
examples/mock-battery/demo-battery-api/src/styles.css
Normal file
156
examples/mock-battery/demo-battery-api/src/styles.css
Normal file
@ -0,0 +1,156 @@
|
||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
|
||||
/**
|
||||
* Mobile First
|
||||
*/
|
||||
body {
|
||||
font: 100%/1.4em "Helvetica Neue", "Helvetica", "Arial", sans-serif;
|
||||
margin: 0 auto;
|
||||
padding: 0 0.625em;
|
||||
color: #444;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small Screens
|
||||
*/
|
||||
|
||||
.demo-header {
|
||||
margin-bottom: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 4em;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.battery-card {
|
||||
font-family: "Helvetica", Arial, sans-serif;
|
||||
display: block;
|
||||
width: 300px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #D5D5D5;
|
||||
border-radius: 6px;
|
||||
font-weight: 100;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.battery-title {
|
||||
background: #4c4c4c url('bolt.png') no-repeat 95% 15px;
|
||||
color: #fff;
|
||||
font-size: .9em;
|
||||
line-height: 50px;
|
||||
padding: 0 15px;
|
||||
font-weight: 100;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.battery-percentage {
|
||||
font-size: 2.5em;
|
||||
line-height: 50px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.battery-box {
|
||||
margin: 0 auto;
|
||||
padding: 50px 0;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #D5D5D5;
|
||||
}
|
||||
|
||||
.battery {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
border: 4px solid #000;
|
||||
width: 85px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.battery:before {
|
||||
content: '';
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
background: #000;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
.battery:after {
|
||||
content: '';
|
||||
display: block;
|
||||
background: #000;
|
||||
width: 6px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -11px;
|
||||
margin-top: -8px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.battery-info {
|
||||
font-size: 12px;
|
||||
margin: 0 auto;
|
||||
padding: 15px 45px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.battery-info dd {
|
||||
float: right;
|
||||
margin-top: -22px;
|
||||
text-align: left;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: 70px auto 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.heart {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
color: #c0392b;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#github-button {
|
||||
display: block;
|
||||
margin: 30px auto 0;
|
||||
position: relative;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
#github-ribbon {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
border: 0;
|
||||
width: 149px;
|
||||
height: 149px;
|
||||
}
|
||||
|
||||
.github-buttons {
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Medium Screens
|
||||
*/
|
||||
@media all and (min-width:40em) {}
|
||||
|
||||
/**
|
||||
* Large Screens
|
||||
*/
|
||||
@media all and (min-width: 54em) {}
|
17
examples/mock-battery/package.json
Normal file
17
examples/mock-battery/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "mock-battery",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"start": "http-server -c-1 -p 9900 demo-battery-api"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.19.1",
|
||||
"http-server": "^14.1.0"
|
||||
}
|
||||
}
|
16
examples/mock-battery/playwright.config.js
Normal file
16
examples/mock-battery/playwright.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
* @type{import('@playwright/test').PlaywrightTestConfig}
|
||||
*/
|
||||
const config = {
|
||||
webServer: {
|
||||
port: 9900,
|
||||
command: 'npm run start',
|
||||
},
|
||||
// Test directory
|
||||
testDir: path.join(__dirname, 'tests'),
|
||||
};
|
||||
module.exports = config;
|
26
examples/mock-battery/tests/show-battery-status.spec.js
Normal file
26
examples/mock-battery/tests/show-battery-status.spec.js
Normal file
@ -0,0 +1,26 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.addInitScript(() => {
|
||||
const mockBattery = {
|
||||
level: 0.90,
|
||||
charging: true,
|
||||
chargingTime: 1800, // seconds
|
||||
dischargingTime: Infinity,
|
||||
addEventListener: () => { }
|
||||
};
|
||||
// application tries navigator.battery first
|
||||
// so we delete this method
|
||||
delete window.navigator.battery;
|
||||
// Override the method to always return mock battery info.
|
||||
window.navigator.getBattery = async () => mockBattery;
|
||||
});
|
||||
});
|
||||
|
||||
test('show battery status', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('.battery-percentage')).toHaveText('90%');
|
||||
await expect(page.locator('.battery-status')).toHaveText('Adapter');
|
||||
await expect(page.locator('.battery-fully')).toHaveText('00:30');
|
||||
})
|
81
examples/mock-battery/tests/update-battery-status.spec.js
Normal file
81
examples/mock-battery/tests/update-battery-status.spec.js
Normal file
@ -0,0 +1,81 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
let log = [];
|
||||
|
||||
test.beforeEach(async ({page}) => {
|
||||
log = [];
|
||||
// Expose function for pushing messages to the Node.js script.
|
||||
await page.exposeFunction('logCall', msg => log.push(msg));
|
||||
|
||||
await page.addInitScript(() => {
|
||||
// for these tests, return the same mock battery status
|
||||
class BatteryMock {
|
||||
level = 0.10;
|
||||
charging = false;
|
||||
chargingTime = 1800;
|
||||
dischargingTime = Infinity;
|
||||
_chargingListeners = [];
|
||||
_levelListeners = [];
|
||||
addEventListener(eventName, listener) {
|
||||
logCall(`addEventListener:${eventName}`);
|
||||
if (eventName === 'chargingchange')
|
||||
this._chargingListeners.push(listener);
|
||||
if (eventName === 'levelchange')
|
||||
this._levelListeners.push(listener);
|
||||
}
|
||||
_setLevel(value) {
|
||||
this.level = value;
|
||||
this._levelListeners.forEach(cb => cb());
|
||||
}
|
||||
_setCharging(value) {
|
||||
this.charging = value;
|
||||
this._chargingListeners.forEach(cb => cb());
|
||||
}
|
||||
};
|
||||
const mockBattery = new BatteryMock();
|
||||
// Override the method to always return mock battery info.
|
||||
window.navigator.getBattery = async () => {
|
||||
logCall('getBattery');
|
||||
return mockBattery;
|
||||
};
|
||||
// Save the mock object on window for easier access.
|
||||
window.mockBattery = mockBattery;
|
||||
|
||||
// application tries navigator.battery first
|
||||
// so we delete this method
|
||||
delete window.navigator.battery;
|
||||
});
|
||||
});
|
||||
|
||||
test('should update UI when battery status changes', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('.battery-percentage')).toHaveText('10%');
|
||||
|
||||
// Update level to 27.5%
|
||||
await page.evaluate(() => window.mockBattery._setLevel(0.275));
|
||||
await expect(page.locator('.battery-percentage')).toHaveText('27.5%');
|
||||
await expect(page.locator('.battery-status')).toHaveText('Battery');
|
||||
|
||||
// Emulate connected adapter
|
||||
await page.evaluate(() => window.mockBattery._setCharging(true));
|
||||
await expect(page.locator('.battery-status')).toHaveText('Adapter');
|
||||
await expect(page.locator('.battery-fully')).toHaveText('00:30');
|
||||
});
|
||||
|
||||
|
||||
test('verify API calls', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('.battery-percentage')).toHaveText('10%');
|
||||
|
||||
// Ensure expected method calls were made.
|
||||
expect(log).toEqual([
|
||||
'getBattery',
|
||||
'addEventListener:chargingchange',
|
||||
'addEventListener:levelchange'
|
||||
]);
|
||||
log = []; // reset the log
|
||||
|
||||
await page.evaluate(() => window.mockBattery._setLevel(0.275));
|
||||
expect(log).toEqual([]); // getBattery is not called, cached version is used.
|
||||
});
|
39
examples/mock-battery/tests/verify-calls.spec.js
Normal file
39
examples/mock-battery/tests/verify-calls.spec.js
Normal file
@ -0,0 +1,39 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
let log = [];
|
||||
|
||||
test.beforeEach(async ({page}) => {
|
||||
log = [];
|
||||
// Expose function for pushing messages to the Node.js script.
|
||||
await page.exposeFunction('logCall', msg => log.push(msg));
|
||||
await page.addInitScript(() => {
|
||||
const mockBattery = {
|
||||
level: 0.75,
|
||||
charging: true,
|
||||
chargingTime: 1800, // seconds
|
||||
dischargingTime: Infinity,
|
||||
addEventListener: (name, cb) => logCall(`addEventListener:${name}`)
|
||||
};
|
||||
// Override the method to always return mock battery info.
|
||||
window.navigator.getBattery = async () => {
|
||||
logCall('getBattery');
|
||||
return mockBattery;
|
||||
};
|
||||
// application tries navigator.battery first
|
||||
// so we delete this method
|
||||
delete window.navigator.battery;
|
||||
});
|
||||
})
|
||||
|
||||
test('verify battery calls', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('.battery-percentage')).toHaveText('75%');
|
||||
|
||||
// Ensure expected method calls were made.
|
||||
expect(log).toEqual([
|
||||
'getBattery',
|
||||
'addEventListener:chargingchange',
|
||||
'addEventListener:levelchange'
|
||||
]);
|
||||
});
|
17
examples/mock-filesystem/package.json
Normal file
17
examples/mock-filesystem/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "mock-filesystem",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"start": "http-server -c-1 -p 9900 src/"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.19.1",
|
||||
"http-server": "^14.1.0"
|
||||
}
|
||||
}
|
16
examples/mock-filesystem/playwright.config.js
Normal file
16
examples/mock-filesystem/playwright.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
* @type{import('@playwright/test').PlaywrightTestConfig}
|
||||
*/
|
||||
const config = {
|
||||
webServer: {
|
||||
port: 9900,
|
||||
command: 'npm run start',
|
||||
},
|
||||
// Test directory
|
||||
testDir: path.join(__dirname, 'tests'),
|
||||
};
|
||||
module.exports = config;
|
15
examples/mock-filesystem/src/file-picker.html
Normal file
15
examples/mock-filesystem/src/file-picker.html
Normal file
@ -0,0 +1,15 @@
|
||||
<head>
|
||||
<script>
|
||||
async function loadFile() {
|
||||
const [fileHandle] = await window.showOpenFilePicker();
|
||||
const file = await fileHandle.getFile();
|
||||
const contents = await file.text();
|
||||
document.getElementById('contents').textContent = contents
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="loadFile()">Open File</button>
|
||||
<p>Pick a text file and its contents will be shown below</p>
|
||||
<textarea id="contents" placeholder="File contents"></textarea>
|
||||
</body>
|
35
examples/mock-filesystem/src/ls-dir.html
Normal file
35
examples/mock-filesystem/src/ls-dir.html
Normal file
@ -0,0 +1,35 @@
|
||||
<head>
|
||||
<style>
|
||||
div.directory::before {
|
||||
content: '📁';
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
async function loadDir() {
|
||||
const handle = await window.showDirectoryPicker();
|
||||
for await (const entry of handle.values())
|
||||
await listEntry(entry, 0);
|
||||
}
|
||||
// List filesystem entry recursively
|
||||
async function listEntry(e, offset) {
|
||||
offset += 2;
|
||||
printEntry(e.name, offset, e.kind);
|
||||
if (e.kind !== 'directory')
|
||||
return;
|
||||
for await (const entry of e.values())
|
||||
await listEntry(entry, offset);
|
||||
}
|
||||
function printEntry(text, offset, kind) {
|
||||
const div = document.createElement('div');
|
||||
div.style.paddingLeft = offset * 10;
|
||||
div.classList.add(kind);
|
||||
div.textContent = text;
|
||||
document.getElementById('dir').appendChild(div);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="ls-directory" onclick="loadDir()">Open directory</button>
|
||||
<p>Directory contents:</p>
|
||||
<div id="dir"></div>
|
||||
</body>
|
64
examples/mock-filesystem/tests/directory-reader.spec.js
Normal file
64
examples/mock-filesystem/tests/directory-reader.spec.js
Normal file
@ -0,0 +1,64 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.beforeEach(async ({page}) => {
|
||||
await page.addInitScript(() => {
|
||||
class FileSystemHandleMock {
|
||||
constructor({name, children}) {
|
||||
this.name = name;
|
||||
children ??= [];
|
||||
this.kind = children.length ? 'directory' : 'file';
|
||||
this._children = children;
|
||||
}
|
||||
|
||||
values() {
|
||||
// Wrap children data in the same mock.
|
||||
return this._children.map(c => new FileSystemHandleMock(c));
|
||||
}
|
||||
}
|
||||
// Create mock directory
|
||||
const mockDir = new FileSystemHandleMock({
|
||||
name: 'root',
|
||||
children: [
|
||||
{
|
||||
name: 'file1',
|
||||
},
|
||||
{
|
||||
name: 'dir1',
|
||||
children: [
|
||||
{
|
||||
name: 'file2',
|
||||
},
|
||||
{
|
||||
name: 'file3',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'dir2',
|
||||
children: [
|
||||
{
|
||||
name: 'file4',
|
||||
},
|
||||
{
|
||||
name: 'file5',
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
// Make the picker return mock directory
|
||||
window.showDirectoryPicker = async () => mockDir;
|
||||
});
|
||||
});
|
||||
|
||||
test('should display directory tree', async ({ page }) => {
|
||||
await page.goto('/ls-dir.html');
|
||||
await page.locator('button', { hasText: 'Open directory' }).click();
|
||||
// Check that the displayed entries match mock directory.
|
||||
await expect(page.locator('#dir')).toContainText([
|
||||
'file1',
|
||||
'dir1', 'file2', 'file3',
|
||||
'dir2', 'file4', 'file5'
|
||||
]);
|
||||
});
|
24
examples/mock-filesystem/tests/file-reader.spec.js
Normal file
24
examples/mock-filesystem/tests/file-reader.spec.js
Normal file
@ -0,0 +1,24 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.beforeEach(async ({page}) => {
|
||||
await page.addInitScript(() => {
|
||||
class FileSystemFileHandleMock {
|
||||
constructor(file) {
|
||||
this._file = file;
|
||||
}
|
||||
|
||||
async getFile() {
|
||||
return this._file;
|
||||
}
|
||||
}
|
||||
window.showOpenFilePicker = async () => [new FileSystemFileHandleMock(new File(['Test content.'], "foo.txt"))];
|
||||
});
|
||||
});
|
||||
|
||||
test('show file picker with mock class', async ({ page }) => {
|
||||
await page.goto('/file-picker.html');
|
||||
await page.locator('button', { hasText: 'Open File' }).click();
|
||||
// Check that the content of the mock file has been loaded.
|
||||
await expect(page.locator('textarea')).toHaveValue('Test content.');
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user