chore: roll folio to 0.3.6 (#4110)

This commit is contained in:
Dmitry Gozman 2020-10-12 09:16:02 -07:00 committed by GitHub
parent 80ed407033
commit 46b14bc740
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 778 additions and 793 deletions

View File

@ -37,10 +37,10 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell.
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx test-runner test/ --jobs=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json && npm run coverage"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx folio test/ --workers=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json && npm run coverage"
env:
BROWSER: ${{ matrix.browser }}
PWRUNNER_JSON_REPORT: "test-results/report.json"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: always()
with:
@ -63,10 +63,10 @@ jobs:
- uses: microsoft/playwright-github-action@v1
- run: npm ci
- run: npm run build
- run: npx test-runner test/ --jobs=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json --shard=${{ matrix.shard }}/2
- run: npx folio test/ --workers=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json --shard=${{ matrix.shard }}/2
env:
BROWSER: ${{ matrix.browser }}
PWRUNNER_JSON_REPORT: "test-results/report.json"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -91,11 +91,11 @@ jobs:
- uses: microsoft/playwright-github-action@v1
- run: npm ci
- run: npm run build
- run: npx test-runner test/ --jobs=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json
- run: npx folio test/ --workers=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json
shell: bash
env:
BROWSER: ${{ matrix.browser }}
PWRUNNER_JSON_REPORT: "test-results/report.json"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -142,12 +142,12 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell.
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx test-runner test/ --jobs=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx folio test/ --workers=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
if: ${{ always() }}
env:
BROWSER: ${{ matrix.browser }}
HEADLESS: "false"
PWRUNNER_JSON_REPORT: "test-results/report.json"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -175,11 +175,11 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell.
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx test-runner test/ --jobs=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx folio test/ --workers=1 --forbid-only --timeout=30000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
env:
BROWSER: ${{ matrix.browser }}
PWWIRE: true
PWRUNNER_JSON_REPORT: "test-results/report.json"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -207,11 +207,11 @@ jobs:
# XVFB-RUN merges both STDOUT and STDERR, whereas we need only STDERR
# Wrap `npm run` in a subshell to redirect STDERR to file.
# Enable core dumps in the subshell.
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx test-runner test/ --jobs=1 --forbid-only --timeout=60000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npx folio test/ --workers=1 --forbid-only --timeout=60000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
env:
BROWSER: ${{ matrix.browser }}
TRACING: true
PWRUNNER_JSON_REPORT: "test-results/report.json"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:

78
package-lock.json generated
View File

@ -1159,33 +1159,6 @@
}
}
},
"@playwright/test-runner": {
"version": "0.9.22",
"resolved": "https://registry.npmjs.org/@playwright/test-runner/-/test-runner-0.9.22.tgz",
"integrity": "sha512-U1RcwMUcL2dBKc4pa7zb0s43jPNRySeXV7QxTT4F7uiyVQp6VLxH/1CU36MM9iiBR43X6rGSdsjmD3FDzc3piw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"@babel/core": "^7.11.4",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.10.4",
"colors": "^1.4.0",
"commander": "^6.1.0",
"debug": "^4.1.5",
"expect": "^26.4.2",
"jpeg-js": "^0.4.2",
"micromatch": "^4.0.2",
"ms": "^2.1.2",
"pirates": "^4.0.1",
"pixelmatch": "^5.2.1",
"pngjs": "^5.0.0",
"pretty-format": "^26.4.2",
"rimraf": "^3.0.2",
"source-map-support": "^0.5.19",
"stack-utils": "^2.0.2"
}
},
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@ -1345,9 +1318,9 @@
}
},
"@types/yargs": {
"version": "15.0.7",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
"integrity": "sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA==",
"version": "15.0.8",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.8.tgz",
"integrity": "sha512-b0BYzFUzBpOhPjpl1wtAHU994jBeKF4TKVlT7ssFv44T617XNcPdRoG4AzHLVshLzlrF7i3lTelH7UbuNYV58Q==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@ -2225,9 +2198,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001144",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001144.tgz",
"integrity": "sha512-4GQTEWNMnVZVOFG3BK0xvGeaDAtiPAbG2N8yuMXuXzx/c2Vd4XoMPO8+E918zeXn5IF0FRVtGShBfkfQea2wHQ==",
"version": "1.0.30001148",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz",
"integrity": "sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw==",
"dev": true
},
"chalk": {
@ -2858,9 +2831,9 @@
}
},
"electron-to-chromium": {
"version": "1.3.577",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.577.tgz",
"integrity": "sha512-dSb64JQSFif/pD8mpVAgSFkbVi6YHbK6JeEziwNNmXlr/Ne2rZtseFK5SM7JoWSLf6gP0gVvRGi4/2ZRhSX/rA==",
"version": "1.3.578",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz",
"integrity": "sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q==",
"dev": true
},
"elliptic": {
@ -3296,9 +3269,9 @@
}
},
"expect": {
"version": "26.5.2",
"resolved": "https://registry.npmjs.org/expect/-/expect-26.5.2.tgz",
"integrity": "sha512-ccTGrXZd8DZCcvCz4htGXTkd/LOoy6OEtiDS38x3/VVf6E4AQL0QoeksBiw7BtGR5xDNiRYPB8GN6pfbuTOi7w==",
"version": "26.5.3",
"resolved": "https://registry.npmjs.org/expect/-/expect-26.5.3.tgz",
"integrity": "sha512-kkpOhGRWGOr+TEFUnYAjfGvv35bfP+OlPtqPIJpOCR9DVtv8QV+p8zG0Edqafh80fsjeE+7RBcVUq1xApnYglw==",
"dev": true,
"requires": {
"@jest/types": "^26.5.2",
@ -3676,6 +3649,33 @@
"readable-stream": "^2.3.6"
}
},
"folio": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/folio/-/folio-0.3.6.tgz",
"integrity": "sha512-LPi0B9HHxdqCAvwgZOdcmPufJX4PjWbS2VN1QbN3mzapMoM1j+OI30YV5fU3e+4krJn50rKuki5WL6gdRIcJlQ==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"@babel/core": "^7.11.4",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.10.4",
"colors": "^1.4.0",
"commander": "^6.1.0",
"debug": "^4.1.5",
"expect": "^26.4.2",
"jpeg-js": "^0.4.2",
"micromatch": "^4.0.2",
"ms": "^2.1.2",
"pirates": "^4.0.1",
"pixelmatch": "^5.2.1",
"pngjs": "^5.0.0",
"pretty-format": "^26.4.2",
"rimraf": "^3.0.2",
"source-map-support": "^0.5.19",
"stack-utils": "^2.0.2"
}
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",

View File

@ -9,15 +9,15 @@
"node": ">=10.17.0"
},
"scripts": {
"ctest": "cross-env BROWSER=chromium test-runner test/",
"ftest": "cross-env BROWSER=firefox test-runner test/",
"wtest": "cross-env BROWSER=webkit test-runner test/",
"test": "test-runner test/",
"ctest": "cross-env BROWSER=chromium folio test/",
"ftest": "cross-env BROWSER=firefox folio test/",
"wtest": "cross-env BROWSER=webkit folio test/",
"test": "folio test/",
"eslint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe --ext js,ts . || eslint --ext js,ts .",
"tsc": "tsc -p .",
"tsc-installer": "tsc -p ./src/install/tsconfig.json",
"doc": "node utils/doclint/cli.js",
"test-infra": "test-runner utils/doclint/check_public_api/test/test.js && test-runner utils/doclint/preprocessor/test.js",
"test-infra": "folio utils/doclint/check_public_api/test/test.js && folio utils/doclint/preprocessor/test.js",
"lint": "npm run eslint && npm run tsc && npm run doc && npm run check-deps && npm run generate-channels && npm run test-types && npm run test-infra",
"clean": "rimraf lib && rimraf types",
"prepare": "node install-from-github.js",
@ -50,7 +50,6 @@
"ws": "^7.3.1"
},
"devDependencies": {
"@playwright/test-runner": "0.9.22",
"@types/debug": "^4.1.5",
"@types/extract-zip": "^1.6.2",
"@types/mime": "^2.0.3",
@ -69,6 +68,7 @@
"electron": "^9.2.1",
"eslint": "^7.7.0",
"eslint-plugin-notice": "^0.9.10",
"folio": "=0.3.6",
"formidable": "^1.2.2",
"ncp": "^2.0.0",
"node-stream-zip": "^1.11.3",

View File

@ -15,9 +15,9 @@
* limitations under the License.
*/
import { serverFixtures } from './remoteServer.fixture';
import { folio } from './remoteServer.fixture';
import * as fs from 'fs';
const { it, expect, describe } = serverFixtures;
const { it, expect, describe } = folio;
describe('connect', (suite, { wire }) => {
suite.skip(wire);

View File

@ -16,26 +16,20 @@
*/
import domain from 'domain';
import { fixtures as baseFixtures } from './fixtures';
import { folio } from './fixtures';
import type { ChromiumBrowser } from '..';
type DomainFixtures = {
domain: any;
};
const fixtures = baseFixtures.defineWorkerFixtures<DomainFixtures>({
domain: async ({ }, test) => {
const local = domain.create();
local.run(() => { });
let err;
local.on('error', e => err = e);
await test(null);
if (err)
throw err;
}
const fixtures = folio.extend<{ domain: any }, {}>();
fixtures.domain.initWorker(async ({ }, run) => {
const local = domain.create();
local.run(() => { });
let err;
local.on('error', e => err = e);
await run(null);
if (err)
throw err;
});
const { it, expect } = fixtures;
const { it, expect } = fixtures.build();
it('should work', async ({browser}) => {
expect(!!browser['_connection']).toBeTruthy();

View File

@ -14,20 +14,18 @@
* limitations under the License.
*/
import { fixtures as playwrightFixtures } from '../fixtures';
import { folio } from '../fixtures';
const fixtures = playwrightFixtures.overrideWorkerFixtures({
browser: async ({browserType, defaultBrowserOptions}, test) => {
const browser = await browserType.launch({
...defaultBrowserOptions,
args: (defaultBrowserOptions.args || []).concat(['--site-per-process'])
});
await test(browser);
await browser.close();
}
const fixtures = folio.extend();
fixtures.browser.overrideWorker(async ({browserType, defaultBrowserOptions}, run) => {
const browser = await browserType.launch({
...defaultBrowserOptions,
args: (defaultBrowserOptions.args || []).concat(['--site-per-process'])
});
await run(browser);
await browser.close();
});
const { it, expect, describe } = fixtures;
const { it, expect, describe } = fixtures.build();
describe('oopif', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { fixtures as playwrightFixtures } from '../fixtures';
import { folio } from '../fixtures';
import fs from 'fs';
import path from 'path';
import type { ChromiumBrowser } from '../..';
@ -22,13 +22,11 @@ import type { ChromiumBrowser } from '../..';
type TestState = {
outputTraceFile: string;
};
const fixtures = playwrightFixtures.defineTestFixtures<TestState>({
outputTraceFile: async ({ testInfo }, test) => {
await test(testInfo.outputPath(path.join(`trace.json`)));
}
const fixtures = folio.extend<{}, TestState>();
fixtures.outputTraceFile.initTest(async ({ testInfo }, run) => {
await run(testInfo.outputPath(path.join(`trace.json`)));
});
const { it, expect, describe } = fixtures;
const { it, expect, describe } = fixtures.build();
describe('oopif', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');

View File

@ -14,353 +14,355 @@
* limitations under the License.
*/
import { serverFixtures } from './remoteServer.fixture';
const { it, expect, beforeEach } = serverFixtures;
import { folio } from './remoteServer.fixture';
const { it, expect, beforeEach, describe } = folio;
import fs from 'fs';
import path from 'path';
import util from 'util';
beforeEach(async ({server}) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
res.end(`Hello world`);
});
server.setRoute('/downloadWithFilename', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
});
it('should report downloads with acceptDownloads: false', async ({page, server}) => {
await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
let error;
expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`);
expect(download.suggestedFilename()).toBe(`file.txt`);
await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true');
});
it('should report downloads with acceptDownloads: true', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should save to user-specified path', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
await page.close();
});
it('should save to user-specified path without updating original path', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
const originalPath = await download.path();
expect(fs.existsSync(originalPath)).toBeTruthy();
expect(fs.readFileSync(originalPath).toString()).toBe('Hello world');
await page.close();
});
it('should save to two different paths with multiple saveAs calls', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
const anotherUserPath = testInfo.outputPath('download (2).txt');
await download.saveAs(anotherUserPath);
expect(fs.existsSync(anotherUserPath)).toBeTruthy();
expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world');
await page.close();
});
it('should save to overwritten filepath', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const dir = testInfo.outputPath('downloads');
const userPath = path.join(dir, 'download.txt');
await download.saveAs(userPath);
expect((await util.promisify(fs.readdir)(dir)).length).toBe(1);
await download.saveAs(userPath);
expect((await util.promisify(fs.readdir)(dir)).length).toBe(1);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
await page.close();
});
it('should create subdirectories when saving to non-existent user-specified path', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const nestedPath = testInfo.outputPath(path.join('these', 'are', 'directories', 'download.txt'));
await download.saveAs(nestedPath);
expect(fs.existsSync(nestedPath)).toBeTruthy();
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
await page.close();
});
it('should save when connected remotely', (test, { wire }) => {
test.skip(wire);
}, async ({testInfo, server, browserType, remoteServer}) => {
const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const nestedPath = testInfo.outputPath(path.join('these', 'are', 'directories', 'download.txt'));
await download.saveAs(nestedPath);
expect(fs.existsSync(nestedPath)).toBeTruthy();
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
const error = await download.path().catch(e => e);
expect(error.message).toContain('Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.');
await browser.close();
});
it('should error when saving with downloads disabled', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: false });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context');
await page.close();
});
it('should error when saving after deletion', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.delete();
const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Download already deleted. Save before deleting.');
await page.close();
});
it('should error when saving after deletion when connected remotely', (test, { wire }) => {
test.skip(wire);
}, async ({testInfo, server, browserType, remoteServer}) => {
const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.delete();
const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Download already deleted. Save before deleting.');
await browser.close();
});
it('should report non-navigation downloads', async ({browser, server}) => {
// Mac WebKit embedder does not download in this case, although Safari does.
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.end(`Hello world`);
});
const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a download="file.txt" href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
expect(download.suggestedFilename()).toBe(`file.txt`);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it(`should report download path within page.on('download', …) handler for Files`, async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
const onDownloadPath = new Promise<string>(res => {
page.on('download', dl => {
dl.path().then(res);
describe('download event', () => {
beforeEach(async ({server}) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
res.end(`Hello world`);
});
server.setRoute('/downloadWithFilename', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
});
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
await page.click('a');
const path = await onDownloadPath;
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it(`should report download path within page.on('download', …) handler for Blobs`, async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
const onDownloadPath = new Promise<string>(res => {
page.on('download', dl => {
dl.path().then(res);
it('should report downloads with acceptDownloads: false', async ({page, server}) => {
await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
let error;
expect(download.url()).toBe(`${server.PREFIX}/downloadWithFilename`);
expect(download.suggestedFilename()).toBe(`file.txt`);
await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true');
});
it('should report downloads with acceptDownloads: true', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should save to user-specified path', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
await page.close();
});
it('should save to user-specified path without updating original path', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
const originalPath = await download.path();
expect(fs.existsSync(originalPath)).toBeTruthy();
expect(fs.readFileSync(originalPath).toString()).toBe('Hello world');
await page.close();
});
it('should save to two different paths with multiple saveAs calls', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.saveAs(userPath);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
const anotherUserPath = testInfo.outputPath('download (2).txt');
await download.saveAs(anotherUserPath);
expect(fs.existsSync(anotherUserPath)).toBeTruthy();
expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world');
await page.close();
});
it('should save to overwritten filepath', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const dir = testInfo.outputPath('downloads');
const userPath = path.join(dir, 'download.txt');
await download.saveAs(userPath);
expect((await util.promisify(fs.readdir)(dir)).length).toBe(1);
await download.saveAs(userPath);
expect((await util.promisify(fs.readdir)(dir)).length).toBe(1);
expect(fs.existsSync(userPath)).toBeTruthy();
expect(fs.readFileSync(userPath).toString()).toBe('Hello world');
await page.close();
});
it('should create subdirectories when saving to non-existent user-specified path', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const nestedPath = testInfo.outputPath(path.join('these', 'are', 'directories', 'download.txt'));
await download.saveAs(nestedPath);
expect(fs.existsSync(nestedPath)).toBeTruthy();
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
await page.close();
});
it('should save when connected remotely', (test, { wire }) => {
test.skip(wire);
}, async ({testInfo, server, browserType, remoteServer}) => {
const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const nestedPath = testInfo.outputPath(path.join('these', 'are', 'directories', 'download.txt'));
await download.saveAs(nestedPath);
expect(fs.existsSync(nestedPath)).toBeTruthy();
expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world');
const error = await download.path().catch(e => e);
expect(error.message).toContain('Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.');
await browser.close();
});
it('should error when saving with downloads disabled', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: false });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context');
await page.close();
});
it('should error when saving after deletion', async ({testInfo, browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.delete();
const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Download already deleted. Save before deleting.');
await page.close();
});
it('should error when saving after deletion when connected remotely', (test, { wire }) => {
test.skip(wire);
}, async ({testInfo, server, browserType, remoteServer}) => {
const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const userPath = testInfo.outputPath('download.txt');
await download.delete();
const { message } = await download.saveAs(userPath).catch(e => e);
expect(message).toContain('Download already deleted. Save before deleting.');
await browser.close();
});
it('should report non-navigation downloads', async ({browser, server}) => {
// Mac WebKit embedder does not download in this case, although Safari does.
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.end(`Hello world`);
});
});
await page.goto(server.PREFIX + '/download-blob.html');
await page.click('a');
const path = await onDownloadPath;
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should report alt-click downloads', (test, { browserName }) => {
test.fixme(browserName === 'firefox' || browserName === 'webkit');
}, async ({browser, server}) => {
// Firefox does not download on alt-click by default.
// Our WebKit embedder does not download on alt-click, although Safari does.
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.end(`Hello world`);
const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a download="file.txt" href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
expect(download.suggestedFilename()).toBe(`file.txt`);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a', { modifiers: ['Alt']})
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it(`should report download path within page.on('download', …) handler for Files`, async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
const onDownloadPath = new Promise<string>(res => {
page.on('download', dl => {
dl.path().then(res);
});
});
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
await page.click('a');
const path = await onDownloadPath;
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it(`should report download path within page.on('download', …) handler for Blobs`, async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
const onDownloadPath = new Promise<string>(res => {
page.on('download', dl => {
dl.path().then(res);
});
});
await page.goto(server.PREFIX + '/download-blob.html');
await page.click('a');
const path = await onDownloadPath;
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should report alt-click downloads', (test, { browserName }) => {
test.fixme(browserName === 'firefox' || browserName === 'webkit');
}, async ({browser, server}) => {
// Firefox does not download on alt-click by default.
// Our WebKit embedder does not download on alt-click, although Safari does.
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.end(`Hello world`);
});
it('should report new window downloads', (test, { browserName, headful }) => {
test.fixme(browserName === 'chromium' && headful);
}, async ({browser, server}) => {
// TODO: - the test fails in headful Chromium as the popup page gets closed along
// with the session before download completed event arrives.
// - WebKit doesn't close the popup page
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a target=_blank href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
await page.close();
});
const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a', { modifiers: ['Alt']})
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close();
});
it('should delete file', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
await download.delete();
expect(fs.existsSync(path)).toBeFalsy();
await page.close();
});
it('should report new window downloads', (test, { browserName, headful }) => {
test.fixme(browserName === 'chromium' && headful);
}, async ({browser, server}) => {
// TODO: - the test fails in headful Chromium as the popup page gets closed along
// with the session before download completed event arrives.
// - WebKit doesn't close the popup page
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a target=_blank href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
await page.close();
});
it('should expose stream', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const stream = await download.createReadStream();
let content = '';
stream.on('data', data => content += data.toString());
await new Promise(f => stream.on('end', f));
expect(content).toBe('Hello world');
await page.close();
});
it('should delete file', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path = await download.path();
expect(fs.existsSync(path)).toBeTruthy();
await download.delete();
expect(fs.existsSync(path)).toBeFalsy();
await page.close();
});
it('should delete downloads on context destruction', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await page.context().close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
});
it('should expose stream', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const stream = await download.createReadStream();
let content = '';
stream.on('data', data => content += data.toString());
await new Promise(f => stream.on('end', f));
expect(content).toBe('Hello world');
await page.close();
});
it('should delete downloads on browser gone', async ({ server, browserType, defaultBrowserOptions }) => {
const browser = await browserType.launch(defaultBrowserOptions);
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await browser.close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
it('should delete downloads on context destruction', async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await page.context().close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
});
it('should delete downloads on browser gone', async ({ server, browserType, defaultBrowserOptions }) => {
const browser = await browserType.launch(defaultBrowserOptions);
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download1 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const [ download2 ] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const path1 = await download1.path();
const path2 = await download2.path();
expect(fs.existsSync(path1)).toBeTruthy();
expect(fs.existsSync(path2)).toBeTruthy();
await browser.close();
expect(fs.existsSync(path1)).toBeFalsy();
expect(fs.existsSync(path2)).toBeFalsy();
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
});
});

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { fixtures as baseFixtures } from './fixtures';
import { folio } from './fixtures';
import fs from 'fs';
import type { Browser, BrowserContext } from '..';
@ -23,40 +23,40 @@ type TestState = {
downloadsBrowser: Browser;
persistentDownloadsContext: BrowserContext;
};
const fixtures = baseFixtures.defineTestFixtures<TestState>({
downloadsBrowser: async ({ server, browserType, defaultBrowserOptions, testInfo }, test) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
const browser = await browserType.launch({
...defaultBrowserOptions,
downloadsPath: testInfo.outputPath(''),
});
await test(browser);
await browser.close();
},
const fixtures = folio.extend<{}, TestState>();
persistentDownloadsContext: async ({ server, launchPersistent, testInfo }, test) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
const { context, page } = await launchPersistent(
{
downloadsPath: testInfo.outputPath(''),
acceptDownloads: true
}
);
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
await test(context);
await context.close();
},
fixtures.downloadsBrowser.initTest(async ({ server, browserType, defaultBrowserOptions, testInfo }, test) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
const browser = await browserType.launch({
...defaultBrowserOptions,
downloadsPath: testInfo.outputPath(''),
});
await test(browser);
await browser.close();
});
const { it, expect } = fixtures;
fixtures.persistentDownloadsContext.initTest(async ({ server, launchPersistent, testInfo }, test) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
res.end(`Hello world`);
});
const { context, page } = await launchPersistent(
{
downloadsPath: testInfo.outputPath(''),
acceptDownloads: true
}
);
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
await test(context);
await context.close();
});
const { it, expect } = fixtures.build();
it('should keep downloadsPath folder', async ({downloadsBrowser, testInfo, server}) => {
const page = await downloadsBrowser.newPage();

View File

@ -14,8 +14,8 @@
* limitations under the License.
*/
import { electronFixtures } from './electron.fixture';
const { it, expect, describe } = electronFixtures;
import { folio } from './electron.fixture';
const { it, expect, describe } = folio;
import path from 'path';
const electronName = process.platform === 'win32' ? 'electron.cmd' : 'electron';

View File

@ -14,8 +14,8 @@
* limitations under the License.
*/
import { electronFixtures } from './electron.fixture';
const { it, expect, describe } = electronFixtures;
import { folio } from './electron.fixture';
const { it, expect, describe } = folio;
describe('electron window', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { fixtures as baseFixtures } from '../fixtures';
import { folio as base } from '../fixtures';
import type { ElectronApplication, ElectronLauncher, ElectronPage } from '../../electron-types';
import path from 'path';
@ -24,24 +24,25 @@ type TestState = {
application: ElectronApplication;
window: ElectronPage;
};
const fixtures = base.extend<{}, TestState>();
export const electronFixtures = baseFixtures.defineTestFixtures<TestState>({
application: async ({ playwright }, test) => {
const electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', electronName);
const application = await playwright.electron.launch(electronPath, {
args: [path.join(__dirname, 'testApp.js')],
});
await test(application);
await application.close();
},
window: async ({ application }, test) => {
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await test(page);
await page.close();
},
fixtures.application.initTest(async ({ playwright }, run) => {
const electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', electronName);
const application = await playwright.electron.launch(electronPath, {
args: [path.join(__dirname, 'testApp.js')],
});
await run(application);
await application.close();
});
fixtures.window.initTest(async ({ application }, run) => {
const page = await application.newBrowserWindow({ width: 800, height: 600 });
await run(page);
await page.close();
});
export const folio = fixtures.build();
declare module '../../index' {
const electron: ElectronLauncher;
}

View File

@ -15,35 +15,33 @@
* limitations under the License.
*/
import { serverFixtures } from './remoteServer.fixture';
import { folio, RemoteServer } from './remoteServer.fixture';
import { execSync } from 'child_process';
import path from 'path';
import { RemoteServer } from './remoteServer.fixture';
export type FixturesFixtures = {
type FixturesFixtures = {
connectedRemoteServer: RemoteServer;
stallingConnectedRemoteServer: RemoteServer;
};
const fixtures = folio.extend<{}, FixturesFixtures>();
const fixturesFixtures = serverFixtures.defineTestFixtures<FixturesFixtures>({
connectedRemoteServer: async ({browserType, remoteServer, server}, test) => {
const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await test(remoteServer);
await browser.close();
},
stallingConnectedRemoteServer: async ({browserType, stallingRemoteServer, server}, test) => {
const browser = await browserType.connect({ wsEndpoint: stallingRemoteServer.wsEndpoint() });
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await test(stallingRemoteServer);
await browser.close();
},
fixtures.connectedRemoteServer.initTest(async ({browserType, remoteServer, server}, run) => {
const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await run(remoteServer);
await browser.close();
});
const { it, describe, expect } = fixturesFixtures;
fixtures.stallingConnectedRemoteServer.initTest(async ({browserType, stallingRemoteServer, server}, run) => {
const browser = await browserType.connect({ wsEndpoint: stallingRemoteServer.wsEndpoint() });
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await run(stallingRemoteServer);
await browser.close();
});
const { it, describe, expect } = fixtures.build();
it('should close the browser when the node process closes', test => {
test.slow();

View File

@ -24,10 +24,9 @@ import type { Browser, BrowserContext, BrowserType, Page } from '../index';
import { Connection } from '../lib/client/connection';
import { Transport } from '../lib/protocol/transport';
import { installCoverageHooks } from './coverage';
import { fixtures as httpFixtures } from './http.fixtures';
import { fixtures as implFixtures } from './impl.fixtures';
import { fixtures as playwrightFixtures } from './playwright.fixtures';
export { expect, config } from '@playwright/test-runner';
import { folio as httpFolio } from './http.fixtures';
import { folio as playwrightFolio } from './playwright.fixtures';
export { expect, config } from 'folio';
const removeFolderAsync = util.promisify(require('rimraf'));
const mkdtempAsync = util.promisify(fs.mkdtemp);
@ -41,118 +40,126 @@ const getExecutablePath = browserName => {
return process.env.WKPATH;
};
type AllTestFixtures = {
type WireParameters = {
wire: boolean;
};
type WorkerFixtures = {
toImpl: (rpcObject: any) => any;
};
type TestFixtures = {
createUserDataDir: () => Promise<string>;
launchPersistent: (options?: Parameters<BrowserType<Browser>['launchPersistentContext']>[1]) => Promise<{ context: BrowserContext, page: Page }>;
};
export const fixtures = playwrightFixtures
.union(httpFixtures)
.union(implFixtures)
.defineParameter('wire', 'Wire testing mode', !!process.env.PWWIRE || false)
.defineTestFixtures<AllTestFixtures>({
createUserDataDir: async ({ }, runTest) => {
const dirs: string[] = [];
async function createUserDataDir() {
// We do not put user data dir in testOutputPath,
// because we do not want to upload them as test result artifacts.
//
// Additionally, it is impossible to upload user data dir after test run:
// - Firefox removes lock file later, presumably from another watchdog process?
// - WebKit has circular symlinks that makes CI go crazy.
const dir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
dirs.push(dir);
return dir;
}
await runTest(createUserDataDir);
await Promise.all(dirs.map(dir => removeFolderAsync(dir).catch(e => { })));
},
const fixtures = playwrightFolio.union(httpFolio).extend<WorkerFixtures, TestFixtures, WireParameters>();
launchPersistent: async ({ createUserDataDir, defaultBrowserOptions, browserType }, test) => {
let context;
async function launchPersistent(options) {
if (context)
throw new Error('can only launch one persitent context');
const userDataDir = await createUserDataDir();
context = await browserType.launchPersistentContext(userDataDir, { ...defaultBrowserOptions, ...options });
const page = context.pages()[0];
return { context, page };
}
await test(launchPersistent);
if (context)
await context.close();
},
})
.overrideWorkerFixtures({
defaultBrowserOptions: async ({ browserName, headful, slowMo }, runTest) => {
const executablePath = getExecutablePath(browserName);
if (executablePath)
console.error(`Using executable at ${executablePath}`);
await runTest({
executablePath,
handleSIGINT: false,
slowMo,
headless: !headful,
});
},
fixtures.wire.initParameter('Wire testing mode', !!process.env.PWWIRE);
playwright: async ({ browserName, testWorkerIndex, platform, wire }, runTest) => {
assert(platform); // Depend on platform to generate all tests.
const { coverage, uninstall } = installCoverageHooks(browserName);
if (wire) {
require('../lib/utils/utils').setUnderTest();
const connection = new Connection();
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'driver.js'), ['serve'], {
stdio: 'pipe',
detached: true,
});
spawnedProcess.unref();
const onExit = (exitCode, signal) => {
throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`);
};
spawnedProcess.on('exit', onExit);
const transport = new Transport(spawnedProcess.stdin, spawnedProcess.stdout);
connection.onmessage = message => transport.send(JSON.stringify(message));
transport.onmessage = message => connection.dispatch(JSON.parse(message));
const playwrightObject = await connection.waitForObjectWithKnownName('Playwright');
await runTest(playwrightObject);
spawnedProcess.removeListener('exit', onExit);
spawnedProcess.stdin.destroy();
spawnedProcess.stdout.destroy();
spawnedProcess.stderr.destroy();
await teardownCoverage();
} else {
const playwright = require('../index');
await runTest(playwright);
await teardownCoverage();
}
fixtures.createUserDataDir.initTest(async ({ }, run) => {
const dirs: string[] = [];
async function createUserDataDir() {
// We do not put user data dir in testOutputPath,
// because we do not want to upload them as test result artifacts.
//
// Additionally, it is impossible to upload user data dir after test run:
// - Firefox removes lock file later, presumably from another watchdog process?
// - WebKit has circular symlinks that makes CI go crazy.
const dir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-'));
dirs.push(dir);
return dir;
}
await run(createUserDataDir);
await Promise.all(dirs.map(dir => removeFolderAsync(dir).catch(e => { })));
});
async function teardownCoverage() {
uninstall();
const coveragePath = path.join(__dirname, 'coverage-report', testWorkerIndex + '.json');
const coverageJSON = [...coverage.keys()].filter(key => coverage.get(key));
await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true });
await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8');
}
},
})
.overrideTestFixtures({
testParametersPathSegment: async ({ browserName }, runTest) => {
await runTest(browserName);
}
fixtures.launchPersistent.initTest(async ({ createUserDataDir, defaultBrowserOptions, browserType }, run) => {
let context;
async function launchPersistent(options) {
if (context)
throw new Error('can only launch one persitent context');
const userDataDir = await createUserDataDir();
context = await browserType.launchPersistentContext(userDataDir, { ...defaultBrowserOptions, ...options });
const page = context.pages()[0];
return { context, page };
}
await run(launchPersistent);
if (context)
await context.close();
});
fixtures.defaultBrowserOptions.overrideWorker(async ({ browserName, headful, slowMo }, run) => {
const executablePath = getExecutablePath(browserName);
if (executablePath)
console.error(`Using executable at ${executablePath}`);
await run({
executablePath,
handleSIGINT: false,
slowMo,
headless: !headful,
});
});
fixtures.playwright.overrideWorker(async ({ browserName, testWorkerIndex, platform, wire }, run) => {
assert(platform); // Depend on platform to generate all tests.
const { coverage, uninstall } = installCoverageHooks(browserName);
if (wire) {
require('../lib/utils/utils').setUnderTest();
const connection = new Connection();
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'driver.js'), ['serve'], {
stdio: 'pipe',
detached: true,
});
spawnedProcess.unref();
const onExit = (exitCode, signal) => {
throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`);
};
spawnedProcess.on('exit', onExit);
const transport = new Transport(spawnedProcess.stdin, spawnedProcess.stdout);
connection.onmessage = message => transport.send(JSON.stringify(message));
transport.onmessage = message => connection.dispatch(JSON.parse(message));
const playwrightObject = await connection.waitForObjectWithKnownName('Playwright');
await run(playwrightObject);
spawnedProcess.removeListener('exit', onExit);
spawnedProcess.stdin.destroy();
spawnedProcess.stdout.destroy();
spawnedProcess.stderr.destroy();
await teardownCoverage();
} else {
const playwright = require('../index');
await run(playwright);
await teardownCoverage();
}
fixtures.generateParametrizedTests(
async function teardownCoverage() {
uninstall();
const coveragePath = path.join(__dirname, 'coverage-report', testWorkerIndex + '.json');
const coverageJSON = [...coverage.keys()].filter(key => coverage.get(key));
await fs.promises.mkdir(path.dirname(coveragePath), { recursive: true });
await fs.promises.writeFile(coveragePath, JSON.stringify(coverageJSON, undefined, 2), 'utf8');
}
});
fixtures.toImpl.initWorker(async ({ playwright }, run) => {
await run((playwright as any)._toImpl);
});
fixtures.testParametersPathSegment.overrideTest(async ({ browserName }, run) => {
await run(browserName);
});
export const folio = fixtures.build();
folio.generateParametrizedTests(
'platform',
process.env.PWTESTREPORT ? ['win32', 'darwin', 'linux'] : [process.platform as ('win32' | 'linux' | 'darwin')]);
export const it = fixtures.it;
export const fit = fixtures.fit;
export const xit = fixtures.xit;
export const describe = fixtures.describe;
export const fdescribe = fixtures.fdescribe;
export const xdescribe = fixtures.xdescribe;
export const beforeEach = fixtures.beforeEach;
export const afterEach = fixtures.afterEach;
export const beforeAll = fixtures.beforeAll;
export const afterAll = fixtures.afterAll;
export const it = folio.it;
export const fit = folio.fit;
export const xit = folio.xit;
export const describe = folio.describe;
export const fdescribe = folio.fdescribe;
export const xdescribe = folio.xdescribe;
export const beforeEach = folio.beforeEach;
export const afterEach = folio.afterEach;
export const beforeAll = folio.beforeAll;
export const afterAll = folio.afterAll;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { fixtures as baseFixtures } from '@playwright/test-runner';
import { folio as base } from 'folio';
import path from 'path';
import { TestServer } from '../utils/testserver';
@ -28,40 +28,39 @@ type HttpTestFixtures = {
httpsServer: TestServer;
};
export const fixtures = baseFixtures
.defineWorkerFixtures<HttpWorkerFixtures>({
httpService: async ({ testWorkerIndex }, test) => {
const assetsPath = path.join(__dirname, 'assets');
const cachedPath = path.join(__dirname, 'assets', 'cached');
const fixtures = base.extend<HttpWorkerFixtures, HttpTestFixtures>();
fixtures.httpService.initWorker(async ({ testWorkerIndex }, test) => {
const assetsPath = path.join(__dirname, 'assets');
const cachedPath = path.join(__dirname, 'assets', 'cached');
const port = 8907 + testWorkerIndex * 2;
const server = await TestServer.create(assetsPath, port);
server.enableHTTPCache(cachedPath);
const port = 8907 + testWorkerIndex * 2;
const server = await TestServer.create(assetsPath, port);
server.enableHTTPCache(cachedPath);
const httpsPort = port + 1;
const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort);
httpsServer.enableHTTPCache(cachedPath);
const httpsPort = port + 1;
const httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort);
httpsServer.enableHTTPCache(cachedPath);
await test({ server, httpsServer });
await test({ server, httpsServer });
await Promise.all([
server.stop(),
httpsServer.stop(),
]);
},
await Promise.all([
server.stop(),
httpsServer.stop(),
]);
});
asset: async ({ }, test) => {
await test(p => path.join(__dirname, `assets`, p));
},
})
.defineTestFixtures<HttpTestFixtures>({
server: async ({ httpService }, test) => {
httpService.server.reset();
await test(httpService.server);
},
fixtures.asset.initWorker(async ({ }, test) => {
await test(p => path.join(__dirname, `assets`, p));
});
httpsServer: async ({ httpService }, test) => {
httpService.httpsServer.reset();
await test(httpService.httpsServer);
},
});
fixtures.server.initTest(async ({ httpService }, test) => {
httpService.server.reset();
await test(httpService.server);
});
fixtures.httpsServer.initTest(async ({ httpService }, test) => {
httpService.httpsServer.reset();
await test(httpService.httpsServer);
});
export const folio = fixtures.build();

View File

@ -1,28 +0,0 @@
/**
* Copyright Microsoft Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fixtures as playwrightFixtures } from './playwright.fixtures';
type ImplWorkerFixtures = {
toImpl: (rpcObject: any) => any;
};
export const fixtures = playwrightFixtures
.defineWorkerFixtures<ImplWorkerFixtures>({
toImpl: async ({ playwright }, test) => {
await test((playwright as any)._toImpl);
}
});

View File

@ -14,10 +14,29 @@
* limitations under the License.
*/
import { config, fixtures as baseFixtures } from '@playwright/test-runner';
import { config, folio as base } from 'folio';
import type { Browser, BrowserContext, BrowserContextOptions, BrowserType, LaunchOptions, Page } from '../index';
import * as path from 'path';
// Parameters ------------------------------------------------------------------
// ... these can be used to run tests in different modes.
type PlaywrightParameters = {
// Browser type name.
browserName: 'chromium' | 'firefox' | 'webkit';
// Whether to run tests headless or headful.
headful: boolean;
// Operating system.
platform: 'win32' | 'linux' | 'darwin';
// Generate screenshot on failure.
screenshotOnFailure: boolean;
// Slows down Playwright operations by the specified amount of milliseconds.
slowMo: number;
// Whether to record the execution trace.
trace: boolean;
};
// Worker fixture declarations -------------------------------------------------
// ... these live as long as the worker process.
@ -58,115 +77,114 @@ type PlaywrightTestFixtures = {
page: Page;
};
export const fixtures = baseFixtures
.defineParameter('browserName', 'Browser type name', (process.env.BROWSER || 'chromium') as 'chromium' | 'firefox' | 'webkit')
.defineParameter('headful', 'Whether to run tests headless or headful', process.env.HEADFUL ? true : false)
.defineParameter('platform', 'Operating system', process.platform as ('win32' | 'linux' | 'darwin'))
.defineParameter('screenshotOnFailure', 'Generate screenshot on failure', false)
.defineParameter('slowMo', 'Slows down Playwright operations by the specified amount of milliseconds', 0)
.defineParameter('trace', 'Whether to record the execution trace', !!process.env.TRACING || false)
.defineWorkerFixtures<PlaywrightWorkerFixtures>({
defaultBrowserOptions: async ({ headful, slowMo }, runTest) => {
await runTest({
handleSIGINT: false,
slowMo,
headless: !headful,
});
},
const fixtures = base.extend<PlaywrightWorkerFixtures, PlaywrightTestFixtures, PlaywrightParameters>();
fixtures.browserName.initParameter('Browser type name', (process.env.BROWSER || 'chromium') as 'chromium' | 'firefox' | 'webkit');
fixtures.headful.initParameter('Whether to run tests headless or headful', process.env.HEADFUL ? true : false);
fixtures.platform.initParameter('Operating system', process.platform as ('win32' | 'linux' | 'darwin'));
fixtures.screenshotOnFailure.initParameter('Generate screenshot on failure', false);
fixtures.slowMo.initParameter('Slows down Playwright operations by the specified amount of milliseconds', 0);
fixtures.trace.initParameter('Whether to record the execution trace', !!process.env.TRACING);
playwright: async ({ }, runTest) => {
const playwright = require('../index');
await runTest(playwright);
},
fixtures.defaultBrowserOptions.initWorker(async ({ headful, slowMo }, run) => {
await run({
handleSIGINT: false,
slowMo,
headless: !headful,
});
});
browserType: async ({ playwright, browserName }, runTest) => {
const browserType = (playwright as any)[browserName];
await runTest(browserType);
},
fixtures.playwright.initWorker(async ({ }, run) => {
const playwright = require('../index');
await run(playwright);
});
browser: async ({ browserType, defaultBrowserOptions }, runTest) => {
const browser = await browserType.launch(defaultBrowserOptions);
await runTest(browser);
await browser.close();
},
fixtures.browserType.initWorker(async ({ playwright, browserName }, run) => {
const browserType = (playwright as any)[browserName];
await run(browserType);
});
isChromium: async ({ browserName }, runTest) => {
await runTest(browserName === 'chromium');
},
fixtures.browser.initWorker(async ({ browserType, defaultBrowserOptions }, run) => {
const browser = await browserType.launch(defaultBrowserOptions);
await run(browser);
await browser.close();
});
isFirefox: async ({ browserName }, runTest) => {
await runTest(browserName === 'firefox');
},
fixtures.isChromium.initWorker(async ({ browserName }, run) => {
await run(browserName === 'chromium');
});
isWebKit: async ({ browserName }, runTest) => {
await runTest(browserName === 'webkit');
},
fixtures.isFirefox.initWorker(async ({ browserName }, run) => {
await run(browserName === 'firefox');
});
isWindows: async ({ platform }, test) => {
await test(platform === 'win32');
},
fixtures.isWebKit.initWorker(async ({ browserName }, run) => {
await run(browserName === 'webkit');
});
isMac: async ({ platform }, test) => {
await test(platform === 'darwin');
},
fixtures.isWindows.initWorker(async ({ platform }, run) => {
await run(platform === 'win32');
});
isLinux: async ({ platform }, test) => {
await test(platform === 'linux');
},
})
.defineTestFixtures<PlaywrightTestFixtures>({
defaultContextOptions: async ({ trace, testInfo }, runTest) => {
if (trace || testInfo.retry) {
await runTest({
_traceResourcesPath: path.join(config.outputDir, 'trace-resources'),
_tracePath: testInfo.outputPath('playwright.trace'),
videosPath: testInfo.outputPath(''),
} as any);
} else {
await runTest({});
}
},
fixtures.isMac.initWorker(async ({ platform }, run) => {
await run(platform === 'darwin');
});
contextFactory: async ({ browser, defaultContextOptions, testInfo, screenshotOnFailure }, runTest) => {
const contexts: BrowserContext[] = [];
async function contextFactory(options: BrowserContextOptions = {}) {
const context = await browser.newContext({ ...defaultContextOptions, ...options });
contexts.push(context);
return context;
}
await runTest(contextFactory);
fixtures.isLinux.initWorker(async ({ platform }, run) => {
await run(platform === 'linux');
});
if (screenshotOnFailure && (testInfo.status !== testInfo.expectedStatus)) {
let ordinal = 0;
for (const context of contexts) {
for (const page of context.pages())
await page.screenshot({ timeout: 5000, path: testInfo.outputPath + `-test-failed-${++ordinal}.png` });
}
}
for (const context of contexts)
await context.close();
},
fixtures.defaultContextOptions.initTest(async ({ trace, testInfo }, run) => {
if (trace || testInfo.retry) {
await run({
_traceResourcesPath: path.join(config.outputDir, 'trace-resources'),
_tracePath: testInfo.outputPath('playwright.trace'),
videosPath: testInfo.outputPath(''),
} as any);
} else {
await run({});
}
});
context: async ({ contextFactory }, runTest) => {
const context = await contextFactory();
await runTest(context);
// Context factory is taking care of closing the context,
// so that it could capture a screenshot on failure.
},
fixtures.contextFactory.initTest(async ({ browser, defaultContextOptions, testInfo, screenshotOnFailure }, run) => {
const contexts: BrowserContext[] = [];
async function contextFactory(options: BrowserContextOptions = {}) {
const context = await browser.newContext({ ...defaultContextOptions, ...options });
contexts.push(context);
return context;
}
await run(contextFactory);
page: async ({ context }, runTest) => {
// Always create page off context so that they matched.
await runTest(await context.newPage());
// Context fixture is taking care of closing the page.
},
})
.overrideTestFixtures({
testParametersPathSegment: async ({ browserName, platform }, runTest) => {
await runTest(browserName + '-' + platform);
}
});
if (screenshotOnFailure && (testInfo.status !== testInfo.expectedStatus)) {
let ordinal = 0;
for (const context of contexts) {
for (const page of context.pages())
await page.screenshot({ timeout: 5000, path: testInfo.outputPath + `-test-failed-${++ordinal}.png` });
}
}
for (const context of contexts)
await context.close();
});
fixtures.context.initTest(async ({ contextFactory }, run) => {
const context = await contextFactory();
await run(context);
// Context factory is taking care of closing the context,
// so that it could capture a screenshot on failure.
});
fixtures.page.initTest(async ({ context }, run) => {
// Always create page off context so that they matched.
await run(await context.newPage());
// Context fixture is taking care of closing the page.
});
fixtures.testParametersPathSegment.overrideTest(async ({ browserName, platform }, run) => {
await run(browserName + '-' + platform);
});
export const folio = fixtures.build();
// If browser is not specified, we are running tests against all three browsers.
fixtures.generateParametrizedTests(
folio.generateParametrizedTests(
'browserName',
process.env.BROWSER ? [process.env.BROWSER] as any : ['chromium', 'webkit', 'firefox']);

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { fixtures as baseFixtures } from './fixtures';
import { folio as base } from './fixtures';
import path from 'path';
import { spawn } from 'child_process';
@ -24,23 +24,24 @@ type ServerFixtures = {
remoteServer: RemoteServer;
stallingRemoteServer: RemoteServer;
};
const fixtures = base.extend<{}, ServerFixtures>();
export const serverFixtures = baseFixtures.defineTestFixtures<ServerFixtures>({
remoteServer: async ({ browserType, defaultBrowserOptions }, test) => {
const remoteServer = new RemoteServer();
await remoteServer._start(browserType, defaultBrowserOptions);
await test(remoteServer);
await remoteServer.close();
},
stallingRemoteServer: async ({ browserType, defaultBrowserOptions }, test) => {
const remoteServer = new RemoteServer();
await remoteServer._start(browserType, defaultBrowserOptions, { stallOnClose: true });
await test(remoteServer);
await remoteServer.close();
},
fixtures.remoteServer.initTest(async ({ browserType, defaultBrowserOptions }, run) => {
const remoteServer = new RemoteServer();
await remoteServer._start(browserType, defaultBrowserOptions);
await run(remoteServer);
await remoteServer.close();
});
fixtures.stallingRemoteServer.initTest(async ({ browserType, defaultBrowserOptions }, run) => {
const remoteServer = new RemoteServer();
await remoteServer._start(browserType, defaultBrowserOptions, { stallOnClose: true });
await run(remoteServer);
await remoteServer.close();
});
export const folio = fixtures.build();
const playwrightPath = path.join(__dirname, '..');
export class RemoteServer {

View File

@ -14,14 +14,12 @@
* limitations under the License.
*/
import { fixtures } from './fixtures';
import { it, expect, describe } from './fixtures';
import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
import { PNG } from 'pngjs';
const { it, expect, describe } = fixtures;
let ffmpegName = '';
if (process.platform === 'win32')
ffmpegName = process.arch === 'ia32' ? 'ffmpeg-win32' : 'ffmpeg-win64';

View File

@ -21,17 +21,16 @@ const checkPublicAPI = require('..');
const Source = require('../../Source');
const mdBuilder = require('../MDBuilder');
const jsBuilder = require('../JSBuilder');
const { fixtures } = require('@playwright/test-runner');
const { defineWorkerFixtures, describe, it, expect } = fixtures;
const { folio } = require('folio');
defineWorkerFixtures({
page: async({}, test) => {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
await test(page);
await browser.close();
}
const fixtures = folio.extend();
fixtures.setWorkerFixture('page', async({}, test) => {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
await test(page);
await browser.close();
});
const { describe, it, expect } = fixtures.build();
describe('checkPublicAPI', function() {
testLint('diff-classes');

View File

@ -16,8 +16,8 @@
const {runCommands} = require('.');
const Source = require('../Source');
const { fixtures } = require('@playwright/test-runner');
const { describe, it, expect } = fixtures;
const { folio } = require('folio');
const { describe, it, expect } = folio;
describe('runCommands', function() {
const OPTIONS_REL = {