test: migrate last tests to new folio (#6071)

This commit is contained in:
Dmitry Gozman 2021-04-05 09:18:56 -07:00 committed by GitHub
parent f21f47889e
commit 4f7e7450e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 495 additions and 414 deletions

View File

@ -14,6 +14,7 @@ env:
# Force terminal colors. @see https://www.npmjs.com/package/colors
FORCE_COLOR: 1
FLAKINESS_CONNECTION_STRING: ${{ secrets.FLAKINESS_CONNECTION_STRING }}
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
jobs:
test_linux:
@ -38,17 +39,8 @@ 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 folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json"
env:
BROWSER: ${{ matrix.browser }}
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run folio -- ${{ matrix.browser }} --reporter=dot,json"
env:
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
# Checking coverage across two test suites is hard. Temporary disabled.
# - run: node test/checkCoverage.js
# env:
# BROWSER: ${{ matrix.browser }}
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run test -- ${{ matrix.browser }} --reporter=dot,json"
- run: node test/checkCoverage.js ${{ matrix.browser }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
- uses: actions/upload-artifact@v1
@ -73,13 +65,7 @@ jobs:
- run: npm ci
- run: npm run build
- run: node lib/cli/cli install-deps ${{ matrix.browser }} chromium
- run: npx folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json
env:
BROWSER: ${{ matrix.browser }}
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: npm run folio -- ${{ matrix.browser }} --reporter=dot,json
env:
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- run: npm run test -- ${{ matrix.browser }} --reporter=dot,json
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
- uses: actions/upload-artifact@v1
@ -106,15 +92,8 @@ jobs:
- run: npm ci
- run: npm run build
- run: node lib/cli/cli install-deps
- run: npx folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json
- run: npm run test -- ${{ matrix.browser }} --reporter=dot,json
shell: bash
env:
BROWSER: ${{ matrix.browser }}
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: npm run folio -- ${{ matrix.browser }} --reporter=dot,json
shell: bash
env:
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
shell: bash
@ -164,17 +143,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 folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json"
if: ${{ always() }}
env:
BROWSER: ${{ matrix.browser }}
HEADFUL: 1
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run folio -- ${{ matrix.browser }} --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run test -- ${{ matrix.browser }} --reporter=dot,json"
if: ${{ always() }}
env:
HEADFUL: 1
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
- uses: actions/upload-artifact@v1
@ -204,15 +176,9 @@ 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 folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json"
env:
BROWSER: "chromium"
PWMODE: "${{ matrix.mode }}"
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run folio -- chromium --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run test -- chromium --reporter=dot,json"
env:
PWMODE: "${{ matrix.mode }}"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
- uses: actions/upload-artifact@v1
@ -242,14 +208,9 @@ 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 folio test/ --workers=1 --forbid-only --timeout=60000 --global-timeout=5400000 --retries=3 --reporter=dot,json -p video"
env:
BROWSER: ${{ matrix.browser }}
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run folio -- ${{ matrix.browser }} --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run test -- ${{ matrix.browser }} --reporter=dot,json"
env:
PWVIDEO: 1
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
- uses: actions/upload-artifact@v1
@ -266,7 +227,6 @@ jobs:
shard: [1, 2]
runs-on: macos-10.15
env:
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
PW_ANDROID_TESTS: 1
steps:
- uses: actions/checkout@v2
@ -281,7 +241,7 @@ jobs:
- name: Start Android Emulator
run: utils/avd_start.sh
- name: Run tests
run: npm run build-folio && node tests/folio/cli.js --config=tests/config/android.config.ts --reporter=dot,json --shard=${{ matrix.shard }}/2
run: npm run atest -- --reporter=dot,json --shard=${{ matrix.shard }}/2
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.repository == 'microsoft/playwright' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
- uses: actions/upload-artifact@v1
@ -315,15 +275,9 @@ 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 folio test/ --workers=1 --forbid-only --timeout=60000 --global-timeout=5400000 --retries=3 --reporter=dot,json"
env:
BROWSER: "chromium"
PW_CHROMIUM_CHANNEL: "chrome"
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run folio -- chromium --reporter=dot,json"
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash -c "ulimit -c unlimited && npm run test -- chromium --reporter=dot,json"
env:
PW_CHROMIUM_CHANNEL: "chrome"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -347,17 +301,10 @@ jobs:
- run: npm run build
# This only created problems, should we move ffmpeg back into npm?
- run: node lib/cli/cli install ffmpeg
- run: npx folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json
shell: bash
env:
BROWSER: "chromium"
PW_CHROMIUM_CHANNEL: "chrome"
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: npm run folio -- chromium --reporter=dot,json
- run: npm run test -- chromium --reporter=dot,json
shell: bash
env:
PW_CHROMIUM_CHANNEL: "chrome"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -378,15 +325,9 @@ jobs:
- run: npm run build
# This only created problems, should we move ffmpeg back into npm?
- run: node lib/cli/cli install ffmpeg
- run: npx folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json
env:
BROWSER: "chromium"
PW_CHROMIUM_CHANNEL: "chrome"
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: npm run folio -- chromium --reporter=dot,json
- run: npm run test -- chromium --reporter=dot,json
env:
PW_CHROMIUM_CHANNEL: "chrome"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:
@ -410,17 +351,10 @@ jobs:
- run: npm run build
# This only created problems, should we move ffmpeg back into npm?
- run: node lib/cli/cli install ffmpeg
- run: npx folio test/ --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json
shell: bash
env:
BROWSER: "chromium"
PW_CHROMIUM_CHANNEL: "msedge"
FOLIO_JSON_OUTPUT_NAME: "test-results/report-old.json"
- run: npm run folio -- chromium --reporter=dot,json
- run: npm run test -- chromium --reporter=dot,json
shell: bash
env:
PW_CHROMIUM_CHANNEL: "msedge"
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
- uses: actions/upload-artifact@v1
if: ${{ always() }}
with:

View File

@ -9,11 +9,11 @@
"node": ">=10.17.0"
},
"scripts": {
"ctest": "cross-env BROWSER=chromium folio test/",
"ftest": "cross-env BROWSER=firefox folio test/",
"wtest": "cross-env BROWSER=webkit folio test/",
"atest": "cross-env BROWSER=chromium PW_ANDROID_TESTS=1 npx folio test/android --workers=1 --reporter=list",
"test": "folio test/",
"ctest": "npm run build-folio && node tests/folio/cli.js --config=tests/config/default.config.ts chromium",
"ftest": "npm run build-folio && node tests/folio/cli.js --config=tests/config/default.config.ts firefox",
"wtest": "npm run build-folio && node tests/folio/cli.js --config=tests/config/default.config.ts webkit",
"atest": "npm run build-folio && node tests/folio/cli.js --config=tests/config/android.config.ts",
"test": "npm run build-folio && node tests/folio/cli.js --config=tests/config/default.config.ts",
"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",
@ -29,8 +29,7 @@
"build-android-driver": "./utils/build_android_driver.sh",
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook -s public",
"build-folio": "tsc -p ./tests/folio",
"folio": "npm run build-folio && node tests/folio/cli.js --config=tests/config/default.config.ts"
"build-folio": "tsc -p ./tests/folio"
},
"author": {
"name": "Microsoft Corporation"

View File

@ -99,15 +99,15 @@ export class RecorderApp extends EventEmitter {
'--window-size=600,600',
'--window-position=1280,10',
];
if (isUnderTest())
args.push(`--remote-debugging-port=0`);
if (process.env.PW_RECORDER_PORT)
args.push(`--remote-debugging-port=${process.env.PW_RECORDER_PORT}`);
const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', {
channel: inspectedContext._browser.options.channel,
sdkLanguage: inspectedContext._options.sdkLanguage,
args,
noDefaultViewport: true,
headless: !!process.env.PWCLI_HEADLESS_FOR_TEST || (isUnderTest() && !inspectedContext._browser.options.headful),
useWebSocket: isUnderTest()
useWebSocket: !!process.env.PW_RECORDER_PORT
});
const controller = new ProgressController(internalCallMetadata(), context._browser);
await controller.run(async progress => {

View File

@ -17,7 +17,7 @@ const path = require('path');
const fs = require('fs');
const {installCoverageHooks} = require('./coverage');
const browserName = process.env.BROWSER || 'chromium';
const browserName = process.argv[2] || 'chromium';
let api = new Set(installCoverageHooks(browserName).coverage.keys());
@ -30,8 +30,21 @@ if (browserName === 'chromium') {
if (browserName !== 'chromium') {
// we don't have CDPSession in non-chromium browsers
api.delete('browser.newBrowserCDPSession');
api.delete('browser.startTracing');
api.delete('browser.stopTracing');
api.delete('browserContext.backgroundPages');
api.delete('browserContext.serviceWorkers');
api.delete('browserContext.newCDPSession');
api.delete('browserContext.emit("backgroundpage")');
api.delete('browserContext.emit("serviceworker")');
api.delete('cDPSession.send');
api.delete('cDPSession.detach');
api.delete('coverage.startJSCoverage');
api.delete('coverage.stopJSCoverage');
api.delete('coverage.startCSSCoverage');
api.delete('coverage.stopCSSCoverage');
api.delete('page.pdf');
}
// Some permissions tests are disabled in webkit. See permissions.jest.js

View File

@ -15,8 +15,7 @@
*/
import { expect } from './fixtures';
import type { Frame, Page, BrowserContext } from '../index';
import { chromium } from '../index';
import type { Frame, Page } from '../index';
export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> {
const handle = await page.evaluateHandle(async ({ frameId, url }) => {
@ -59,12 +58,3 @@ export function expectedSSLError(browserName: string): string {
}
return expectedSSLError;
}
export async function recorderPageGetter(context: BrowserContext, toImpl: (x: any) => any) {
while (!toImpl(context).recorderAppForTest)
await new Promise(f => setTimeout(f, 100));
const wsEndpoint = toImpl(context).recorderAppForTest.wsEndpoint;
const browser = await chromium.connectOverCDP({ wsEndpoint });
const c = browser.contexts()[0];
return c.pages()[0] || await c.waitForEvent('page');
}

View File

@ -14,21 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { it, expect, describe } from '../fixtures';
import { test as pageTest, expect } from '../config/pageTest';
import { test as playwrightTest } from '../config/playwrightTest';
import http from 'http';
describe('chromium', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');
}, () => {
it('should create a worker from a service worker', async ({page, server, context}) => {
pageTest.describe('chromium', () => {
pageTest.beforeEach(async ({ browserName }) => {
pageTest.skip(browserName !== 'chromium');
pageTest.skip(!!process.env.PW_ANDROID_TESTS);
});
pageTest('should create a worker from a service worker', async ({page, server}) => {
const [worker] = await Promise.all([
context.waitForEvent('serviceworker'),
page.context().waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
});
it('serviceWorkers() should return current workers', async ({page, server, context}) => {
pageTest('serviceWorkers() should return current workers', async ({page, server}) => {
const context = page.context();
const [worker1] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
@ -46,31 +52,17 @@ describe('chromium', (suite, { browserName }) => {
expect(workers).toContain(worker2);
});
it('should not create a worker from a shared worker', async ({page, server, context}) => {
pageTest('should not create a worker from a shared worker', async ({page, server}) => {
await page.goto(server.EMPTY_PAGE);
let serviceWorkerCreated;
context.once('serviceworker', () => serviceWorkerCreated = true);
page.context().once('serviceworker', () => serviceWorkerCreated = true);
await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")');
});
expect(serviceWorkerCreated).not.toBeTruthy();
});
it('should close service worker together with the context', async ({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
const [worker] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
const messages = [];
context.on('close', () => messages.push('context'));
worker.on('close', () => messages.push('worker'));
await context.close();
expect(messages.join('|')).toBe('worker|context');
});
it('Page.route should work with intervention headers', async ({server, page}) => {
pageTest('Page.route should work with intervention headers', async ({server, page}) => {
server.setRoute('/intervention', (req, res) => res.end(`
<script>
document.write('<script src="${server.CROSS_PROCESS_PREFIX}/intervention.js">' + '</scr' + 'ipt>');
@ -89,9 +81,31 @@ describe('chromium', (suite, { browserName }) => {
// make it work with Edgium.
expect(serverRequest.headers.intervention).toContain('feature/5718547946799104');
});
});
it('should connect to an existing cdp session', async ({browserType, testWorkerIndex, browserOptions }) => {
const port = 9339 + testWorkerIndex;
playwrightTest.describe('chromium', () => {
playwrightTest.beforeEach(async ({ browserName }) => {
playwrightTest.skip(browserName !== 'chromium');
});
playwrightTest('should close service worker together with the context', async ({browserType, browserOptions, server}) => {
const browser = await browserType.launch(browserOptions);
const context = await browser.newContext();
const page = await context.newPage();
const [worker] = await Promise.all([
context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
const messages = [];
context.on('close', () => messages.push('context'));
worker.on('close', () => messages.push('worker'));
await context.close();
expect(messages.join('|')).toBe('worker|context');
await browser.close();
});
playwrightTest('should connect to an existing cdp session', async ({ browserType, browserOptions }, testInfo) => {
const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({
...browserOptions,
args: ['--remote-debugging-port=' + port]
@ -115,8 +129,8 @@ describe('chromium', (suite, { browserName }) => {
}
});
it('should connect to an existing cdp session twice', async ({browserType, testWorkerIndex, browserOptions, server }) => {
const port = 9339 + testWorkerIndex;
playwrightTest('should connect to an existing cdp session twice', async ({ browserType, browserOptions, server }, testInfo) => {
const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({
...browserOptions,
args: ['--remote-debugging-port=' + port]
@ -157,8 +171,8 @@ describe('chromium', (suite, { browserName }) => {
}
});
it('should connect to existing service workers', async ({browserType, testWorkerIndex, browserOptions, server}) => {
const port = 9339 + testWorkerIndex;
playwrightTest('should connect to existing service workers', async ({browserType, browserOptions, server}, testInfo) => {
const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({
...browserOptions,
args: ['--remote-debugging-port=' + port]

View File

@ -14,12 +14,14 @@
* limitations under the License.
*/
import { it, expect, describe } from './fixtures';
import { test as it, expect } from '../config/pageTest';
describe('CSS Coverage', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');
}, () => {
it('should work', async function({browserType, page, server}) {
it.describe('CSS Coverage', () => {
it.beforeEach(async ({ browserName }) => {
it.skip(browserName !== 'chromium');
});
it('should work', async function({page, server}) {
await page.coverage.startCSSCoverage();
await page.goto(server.PREFIX + '/csscoverage/simple.html');
const coverage = await page.coverage.stopCSSCoverage();

View File

@ -14,11 +14,13 @@
* limitations under the License.
*/
import { it, expect, describe } from './fixtures';
import { test as it, expect } from '../config/pageTest';
it.describe('JS Coverage', () => {
it.beforeEach(async ({ browserName }) => {
it.skip(browserName !== 'chromium');
});
describe('JS Coverage', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');
}, () => {
it('should work', async function({page, server}) {
await page.coverage.startJSCoverage();
await page.goto(server.PREFIX + '/jscoverage/simple.html', { waitUntil: 'load' });

View File

@ -13,31 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { it, expect } from '../fixtures';
import { test as it, expect } from '../config/playwrightTest';
import path from 'path';
it('should throw with remote-debugging-pipe argument', (test, { browserName, mode }) => {
test.skip(mode !== 'default' || browserName !== 'chromium');
}, async ({browserType, browserOptions}) => {
it.beforeEach(async ({ browserName }) => {
it.skip(browserName !== 'chromium');
});
it('should throw with remote-debugging-pipe argument', async ({browserType, browserOptions, mode}) => {
it.skip(mode !== 'default');
const options = Object.assign({}, browserOptions);
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
const error = await browserType.launchServer(options).catch(e => e);
expect(error.message).toContain('Playwright manages remote debugging connection itself');
});
it('should not throw with remote-debugging-port argument', (test, { browserName, mode }) => {
test.skip(mode !== 'default' || browserName !== 'chromium');
}, async ({browserType, browserOptions}) => {
it('should not throw with remote-debugging-port argument', async ({browserType, browserOptions, mode}) => {
it.skip(mode !== 'default');
const options = Object.assign({}, browserOptions);
options.args = ['--remote-debugging-port=0'].concat(options.args || []);
const browser = await browserType.launchServer(options);
await browser.close();
});
it('should open devtools when "devtools: true" option is given', (test, { mode, browserName, platform}) => {
test.skip(browserName !== 'chromium' || mode !== 'default' || platform === 'win32');
}, async ({browserType, browserOptions}) => {
it('should open devtools when "devtools: true" option is given', async ({browserType, browserOptions, mode, platform}) => {
it.skip(mode !== 'default' || platform === 'win32');
let devtoolsCallback;
const devtoolsPromise = new Promise(f => devtoolsCallback = f);
const __testHookForDevTools = devtools => devtools.__testHookOnBinding = parsed => {
@ -53,11 +57,9 @@ it('should open devtools when "devtools: true" option is given', (test, { mode,
await browser.close();
});
it('should return background pages', (test, { browserName }) => {
test.skip(browserName !== 'chromium');
}, async ({browserType, browserOptions, createUserDataDir}) => {
it('should return background pages', async ({browserType, browserOptions, createUserDataDir}) => {
const userDataDir = await createUserDataDir();
const extensionPath = path.join(__dirname, '..', 'assets', 'simple-extension');
const extensionPath = path.join(__dirname, '..', '..', 'test', 'assets', 'simple-extension');
const extensionOptions = {...browserOptions,
headless: false,
args: [
@ -76,11 +78,9 @@ it('should return background pages', (test, { browserName }) => {
await context.close();
});
it('should return background pages when recording video', (test, { browserName }) => {
test.skip(browserName !== 'chromium');
}, async ({browserType, testInfo, browserOptions, createUserDataDir}) => {
it('should return background pages when recording video', async ({browserType, browserOptions, createUserDataDir}, testInfo) => {
const userDataDir = await createUserDataDir();
const extensionPath = path.join(__dirname, '..', 'assets', 'simple-extension');
const extensionPath = path.join(__dirname, '..', '..', 'test', 'assets', 'simple-extension');
const extensionOptions = {...browserOptions,
headless: false,
args: [
@ -102,9 +102,7 @@ it('should return background pages when recording video', (test, { browserName }
await context.close();
});
it('should not create pages automatically', (test, { browserName }) => {
test.skip(browserName !== 'chromium');
}, async ({browserType, browserOptions}) => {
it('should not create pages automatically', async ({browserType, browserOptions}) => {
const browser = await browserType.launch(browserOptions);
const browserSession = await browser.newBrowserCDPSession();
const targets = [];

View File

@ -14,30 +14,41 @@
* limitations under the License.
*/
import { folio } from '../fixtures';
import { test as it, expect } from '../config/playwrightTest';
import type { Browser, Page } from '../../index';
const fixtures = folio.extend();
fixtures.browser.override(async ({browserType, browserOptions}, run) => {
const browser = await browserType.launch({
...browserOptions,
args: (browserOptions.args || []).concat(['--site-per-process'])
it.describe('oopif', () => {
let browser: Browser;
let page: Page;
it.beforeEach(async ({ browserName, browserType, browserOptions }) => {
it.skip(browserName !== 'chromium');
if (!browser) {
browser = await browserType.launch({
...browserOptions,
args: (browserOptions.args || []).concat(['--site-per-process'])
});
}
page = await browser.newPage();
});
await run(browser);
await browser.close();
});
const { it, expect, describe } = fixtures.build();
describe('oopif', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');
}, () => {
it('should report oopif frames', async function({browser, page, server}) {
it.afterEach(async () => {
await page.close();
});
it.afterAll(async () => {
await browser.close();
});
it('should report oopif frames', async function({server}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
expect(page.frames().length).toBe(2);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html');
});
it('should handle oopif detach', async function({browser, page, server}) {
it('should handle oopif detach', async function({server}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
expect(page.frames().length).toBe(2);
@ -50,7 +61,7 @@ describe('oopif', (suite, { browserName }) => {
expect(detachedFrame).toBe(frame);
});
it('should handle remote -> local -> remote transitions', async function({browser, page, server}) {
it('should handle remote -> local -> remote transitions', async function({server}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1);
@ -69,10 +80,9 @@ describe('oopif', (suite, { browserName }) => {
expect(await countOOPIFs(browser)).toBe(1);
});
it('should get the proper viewport', (test, { browserName }) => {
test.fixme(browserName === 'chromium');
test.skip(browserName !== 'chromium');
}, async ({browser, page, server}) => {
it('should get the proper viewport', async ({server}) => {
it.fixme();
expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
@ -91,7 +101,7 @@ describe('oopif', (suite, { browserName }) => {
expect(await oopif.evaluate(() => 'ontouchstart' in window)).toBe(false);
});
it('should expose function', async ({browser, page, server}) => {
it('should expose function', async ({server}) => {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1);
@ -103,7 +113,7 @@ describe('oopif', (suite, { browserName }) => {
expect(result).toBe(36);
});
it('should emulate media', async ({browser, page, server}) => {
it('should emulate media', async ({server}) => {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1);
@ -113,17 +123,17 @@ describe('oopif', (suite, { browserName }) => {
expect(await oopif.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
});
it('should emulate offline', async ({browser, page, context, server}) => {
it('should emulate offline', async ({server}) => {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1);
const oopif = page.frames()[1];
expect(await oopif.evaluate(() => navigator.onLine)).toBe(true);
await context.setOffline(true);
await page.context().setOffline(true);
expect(await oopif.evaluate(() => navigator.onLine)).toBe(false);
});
it('should support context options', async ({browser, server, playwright}) => {
it('should support context options', async ({server, playwright}) => {
const iPhone = playwright.devices['iPhone 6'];
const context = await browser.newContext({ ...iPhone, timezoneId: 'America/Jamaica', locale: 'fr-CH', userAgent: 'UA' });
const page = await context.newPage();
@ -145,7 +155,7 @@ describe('oopif', (suite, { browserName }) => {
await context.close();
});
it('should respect route', async ({browser, page, server}) => {
it('should respect route', async ({server}) => {
let intercepted = false;
await page.route('**/digits/0.png', route => {
intercepted = true;
@ -157,7 +167,7 @@ describe('oopif', (suite, { browserName }) => {
expect(intercepted).toBe(true);
});
it('should take screenshot', async ({browser, page, server}) => {
it('should take screenshot', async ({server}) => {
await page.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2);
@ -165,13 +175,13 @@ describe('oopif', (suite, { browserName }) => {
expect(await page.screenshot()).toMatchSnapshot('screenshot-oopif.png', { threshold: 0.3 });
});
it('should load oopif iframes with subresources and route', async function({browser, page, server, context}) {
it('should load oopif iframes with subresources and route', async function({server}) {
await page.route('**/*', route => route.continue());
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
});
it('should report main requests', async function({browser, page, server}) {
it('should report main requests', async function({server}) {
const requestFrames = [];
page.on('request', r => requestFrames.push(r.frame()));
const finishedFrames = [];
@ -209,8 +219,8 @@ describe('oopif', (suite, { browserName }) => {
expect(finishedFrames[2]).toBe(grandChild);
});
it('should support exposeFunction', async function({browser, context, page, server}) {
await context.exposeFunction('dec', a => a - 1);
it('should support exposeFunction', async function({server}) {
await page.context().exposeFunction('dec', a => a - 1);
await page.exposeFunction('inc', a => a + 1);
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
@ -221,8 +231,8 @@ describe('oopif', (suite, { browserName }) => {
expect(await page.frames()[1].evaluate(() => window['dec'](4))).toBe(3);
});
it('should support addInitScript', async function({browser, context, page, server}) {
await context.addInitScript(() => window['bar'] = 17);
it('should support addInitScript', async function({server}) {
await page.context().addInitScript(() => window['bar'] = 17);
await page.addInitScript(() => window['foo'] = 42);
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
@ -233,7 +243,7 @@ describe('oopif', (suite, { browserName }) => {
expect(await page.frames()[1].evaluate(() => window['bar'])).toBe(17);
});
// @see https://github.com/microsoft/playwright/issues/1240
it('should click a button when it overlays oopif', async function({browser, page, server}) {
it('should click a button when it overlays oopif', async function({server}) {
await page.goto(server.PREFIX + '/button-overlay-oopif.html');
expect(await countOOPIFs(browser)).toBe(1);
await page.click('button');
@ -265,7 +275,7 @@ describe('oopif', (suite, { browserName }) => {
await browser.close();
});
it('ElementHandle.boundingBox() should work', async function({browser, page, server}) {
it('ElementHandle.boundingBox() should work', async function({server}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
await page.$eval('iframe', iframe => {
iframe.style.width = '500px';
@ -288,7 +298,7 @@ describe('oopif', (suite, { browserName }) => {
expect(await handle2.boundingBox()).toEqual({ x: 100 + 42, y: 50 + 17, width: 50, height: 50 });
});
it('should click', async function({browser, page, server}) {
it('should click', async function({server}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html');
await page.$eval('iframe', iframe => {
iframe.style.width = '500px';

View File

@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { it, expect, describe } from '../fixtures';
describe('session', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');
}, () => {
import { test as it, expect } from '../config/pageTest';
import { test as browserTest } from '../config/browserTest';
it.describe('session', () => {
it.beforeEach(async ({ browserName }) => {
it.skip(browserName !== 'chromium');
});
it('should work', async function({page}) {
const client = await page.context().newCDPSession(page);
@ -88,8 +92,14 @@ describe('session', (suite, { browserName }) => {
await client.send('ThisCommand.DoesNotExist');
}
});
});
it('should not break page.close()', async function({browser}) {
browserTest.describe('session', () => {
browserTest.beforeEach(async ({ browserName }) => {
browserTest.skip(browserName !== 'chromium');
});
browserTest('should not break page.close()', async function({browser}) {
const context = await browser.newContext();
const page = await context.newPage();
const session = await page.context().newCDPSession(page);
@ -98,7 +108,7 @@ describe('session', (suite, { browserName }) => {
await context.close();
});
it('should detach when page closes', async function({browser}) {
browserTest('should detach when page closes', async function({browser}) {
const context = await browser.newContext();
const page = await context.newPage();
const session = await context.newCDPSession(page);
@ -109,7 +119,7 @@ describe('session', (suite, { browserName }) => {
await context.close();
});
it('should work with newBrowserCDPSession', async function({browser}) {
browserTest('should work with newBrowserCDPSession', async function({browser}) {
const session = await browser.newBrowserCDPSession();
const version = await session.send('Browser.getVersion');

View File

@ -14,40 +14,48 @@
* limitations under the License.
*/
import { folio } from '../fixtures';
import { test as it, expect } from '../config/browserTest';
import fs from 'fs';
import path from 'path';
const { it, expect, describe } = folio;
describe('tracing', (suite, { browserName }) => {
suite.skip(browserName !== 'chromium');
}, () => {
it('should output a trace', async ({browser, page, server, testInfo}) => {
it.describe('tracing', () => {
it.beforeEach(async ({ browserName }) => {
it.skip(browserName !== 'chromium');
});
it('should output a trace', async ({browser, server}, testInfo) => {
const page = await browser.newPage();
const outputTraceFile = testInfo.outputPath(path.join(`trace.json`));
await browser.startTracing(page, {screenshots: true, path: outputTraceFile});
await page.goto(server.PREFIX + '/grid.html');
await browser.stopTracing();
expect(fs.existsSync(outputTraceFile)).toBe(true);
await page.close();
});
it('should create directories as needed', async ({browser, page, server, testInfo}) => {
it('should create directories as needed', async ({browser, server}, testInfo) => {
const page = await browser.newPage();
const filePath = testInfo.outputPath(path.join('these', 'are', 'directories', 'trace.json'));
await browser.startTracing(page, {screenshots: true, path: filePath});
await page.goto(server.PREFIX + '/grid.html');
await browser.stopTracing();
expect(fs.existsSync(filePath)).toBe(true);
await page.close();
});
it('should run with custom categories if provided', async ({browser, page, testInfo}) => {
it('should run with custom categories if provided', async ({browser}, testInfo) => {
const page = await browser.newPage();
const outputTraceFile = testInfo.outputPath(path.join(`trace.json`));
await browser.startTracing(page, {path: outputTraceFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']});
await browser.stopTracing();
const traceJson = JSON.parse(fs.readFileSync(outputTraceFile).toString());
expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires');
await page.close();
});
it('should throw if tracing on two pages', async ({browser, page, testInfo}) => {
it('should throw if tracing on two pages', async ({browser}, testInfo) => {
const page = await browser.newPage();
const outputTraceFile = testInfo.outputPath(path.join(`trace.json`));
await browser.startTracing(page, {path: outputTraceFile});
const newPage = await browser.newPage();
@ -56,28 +64,35 @@ describe('tracing', (suite, { browserName }) => {
await newPage.close();
expect(error).toBeTruthy();
await browser.stopTracing();
await page.close();
});
it('should return a buffer', async ({browser, page, server, testInfo}) => {
it('should return a buffer', async ({browser, server}, testInfo) => {
const page = await browser.newPage();
const outputTraceFile = testInfo.outputPath(path.join(`trace.json`));
await browser.startTracing(page, {screenshots: true, path: outputTraceFile});
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
const buf = fs.readFileSync(outputTraceFile);
expect(trace.toString()).toEqual(buf.toString());
await page.close();
});
it('should work without options', async ({browser, page, server}) => {
it('should work without options', async ({browser, server}) => {
const page = await browser.newPage();
await browser.startTracing(page);
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
expect(trace).toBeTruthy();
await page.close();
});
it('should support a buffer without a path', async ({browser, page, server}) => {
it('should support a buffer without a path', async ({browser, server}) => {
const page = await browser.newPage();
await browser.startTracing(page, {screenshots: true});
await page.goto(server.PREFIX + '/grid.html');
const trace = await browser.stopTracing();
expect(trace.toString()).toContain('screenshot');
await page.close();
});
});

View File

@ -24,10 +24,10 @@ import { AndroidEnv, AndroidPageEnv } from './androidEnv';
const config: Config = {
testDir: path.join(__dirname, '..'),
timeout: 120000,
globalTimeout: 5400000,
globalTimeout: 7200000,
workers: 1,
};
if (process.env.CI) {
config.workers = 1;
config.forbidOnly = true;
config.retries = 1; // Multiple retries are too slow on Android.
}

View File

@ -58,7 +58,7 @@ class ServiceMode {
private _serviceProcess: childProcess.ChildProcess;
async setup(workerInfo: WorkerInfo) {
const port = 9407 + workerInfo.workerIndex * 2;
const port = 10507 + workerInfo.workerIndex;
this._serviceProcess = childProcess.fork(path.join(__dirname, '..', '..', 'lib', 'service.js'), [String(port)], {
stdio: 'pipe'
});
@ -69,7 +69,6 @@ class ServiceMode {
f();
});
});
this._serviceProcess.unref();
this._serviceProcess.on('exit', this._onExit);
this._client = await PlaywrightClient.connect(`ws://localhost:${port}/ws`);
this._playwrightObejct = this._client.playwright();

View File

@ -18,7 +18,7 @@ import type { Env, TestInfo, WorkerInfo } from '../folio/out';
import { PageEnv } from './browserEnv';
import { CLIMock, CLITestArgs, Recorder } from './cliTest';
import * as http from 'http';
import { recorderPageGetter } from '../../test/utils';
import { chromium } from '../../index';
export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
private _server: http.Server | undefined;
@ -29,8 +29,9 @@ export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
async beforeAll(workerInfo: WorkerInfo) {
await super.beforeAll(workerInfo);
this._port = 9907 + workerInfo.workerIndex;
this._port = 10907 + workerInfo.workerIndex * 2;
this._server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => this._handler(req, res)).listen(this._port);
process.env.PW_RECORDER_PORT = String(this._port + 1);
}
private _runCLI(args: string[]) {
@ -41,6 +42,14 @@ export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
async beforeEach(testInfo: TestInfo) {
const result = await super.beforeEach(testInfo);
const { page, context, toImpl } = result;
const recorderPageGetter = async () => {
while (!toImpl(context).recorderAppForTest)
await new Promise(f => setTimeout(f, 100));
const wsEndpoint = toImpl(context).recorderAppForTest.wsEndpoint;
const browser = await chromium.connectOverCDP({ wsEndpoint });
const c = browser.contexts()[0];
return c.pages()[0] || await c.waitForEvent('page');
};
return {
...result,
httpServer: {
@ -50,9 +59,9 @@ export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
runCLI: this._runCLI.bind(this),
openRecorder: async () => {
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
const recorderPage = await recorderPageGetter(context, toImpl);
return new Recorder(page, recorderPage);
return new Recorder(page, await recorderPageGetter());
},
recorderPageGetter,
};
}

View File

@ -15,7 +15,7 @@
*/
import { newTestType } from '../folio/out';
import type { Page } from '../../index';
import type { Page, BrowserContext } from '../../index';
import type { ServerTestArgs } from './serverTest';
import type { BrowserTestArgs } from './browserTest';
import * as http from 'http';
@ -31,7 +31,9 @@ interface CLIHTTPServer {
export type CLITestArgs = BrowserTestArgs & {
page: Page;
context: BrowserContext;
httpServer: CLIHTTPServer;
recorderPageGetter: () => Promise<Page>;
openRecorder: () => Promise<Recorder>;
runCLI: (args: string[]) => CLIMock;
};

View File

@ -70,7 +70,8 @@ for (const browserName of browsers) {
pageTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {});
// TODO: get rid of contextTest if there isn't too many of them.
contextTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {});
cliTest.runWith(browserName, serverEnv, new CLIEnv(browserName, options), {});
if (mode !== 'service')
cliTest.runWith(browserName, serverEnv, new CLIEnv(browserName, options), {});
if (browserName === 'chromium')
electronTest.runWith(browserName, serverEnv, new ElectronEnv({ mode }));
}

View File

@ -30,7 +30,7 @@ export class ServerEnv implements Env<ServerTestArgs> {
const assetsPath = path.join(__dirname, '..', '..', 'test', 'assets');
const cachedPath = path.join(__dirname, '..', '..', 'test', 'assets', 'cached');
const port = 8907 + workerInfo.workerIndex * 2;
const port = 8907 + workerInfo.workerIndex * 3;
this._server = await TestServer.create(assetsPath, port);
this._server.enableHTTPCache(cachedPath);
@ -54,7 +54,7 @@ export class ServerEnv implements Env<ServerTestArgs> {
].join('\r\n'));
}
});
this._socksPort = 9107 + workerInfo.workerIndex * 2;
this._socksPort = port + 2;
this._socksServer.listen(this._socksPort, 'localhost');
this._socksServer.useAuth(socks.auth.None());
}

View File

@ -14,15 +14,13 @@
* limitations under the License.
*/
import { folio } from './fixtures';
const { it, expect, beforeEach, describe } = folio;
import { test as it, expect } from './config/browserTest';
import fs from 'fs';
import path from 'path';
import util from 'util';
describe('download event', () => {
beforeEach(async ({server}) => {
it.describe('download event', () => {
it.beforeEach(async ({server}) => {
server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
@ -35,7 +33,8 @@ describe('download event', () => {
});
});
it('should report downloads with acceptDownloads: false', async ({page, server}) => {
it('should report downloads with acceptDownloads: false', async ({browser, server}) => {
const page = await browser.newPage();
await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
const [ download ] = await Promise.all([
page.waitForEvent('download'),
@ -47,6 +46,7 @@ describe('download event', () => {
await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true');
await page.close();
});
it('should report downloads with acceptDownloads: true', async ({browser, server}) => {
@ -62,10 +62,9 @@ describe('download event', () => {
await page.close();
});
it('should report proper download url when download is from download attribute', (test, {browserName}) => {
// @see https://github.com/microsoft/playwright/issues/5537
test.fixme(browserName === 'webkit');
}, async ({browser, server}) => {
it('should report proper download url when download is from download attribute', async ({browser, server, browserName}) => {
it.fixme(browserName === 'webkit', '@see https://github.com/microsoft/playwright/issues/5537');
const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.PREFIX + '/empty.html');
await page.setContent(`<a href="${server.PREFIX}/chromium-linux.zip" download="foo.zip">download</a>`);
@ -91,7 +90,7 @@ describe('download event', () => {
await page.close();
});
it('should save to user-specified path', async ({testInfo, browser, server}) => {
it('should save to user-specified path', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -105,7 +104,7 @@ describe('download event', () => {
await page.close();
});
it('should save to user-specified path without updating original path', async ({testInfo, browser, server}) => {
it('should save to user-specified path without updating original path', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -123,7 +122,7 @@ describe('download event', () => {
await page.close();
});
it('should save to two different paths with multiple saveAs calls', async ({testInfo, browser, server}) => {
it('should save to two different paths with multiple saveAs calls', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -142,7 +141,7 @@ describe('download event', () => {
await page.close();
});
it('should save to overwritten filepath', async ({testInfo, browser, server}) => {
it('should save to overwritten filepath', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -160,7 +159,7 @@ describe('download event', () => {
await page.close();
});
it('should create subdirectories when saving to non-existent user-specified path', async ({testInfo, browser, server}) => {
it('should create subdirectories when saving to non-existent user-specified path', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -174,7 +173,7 @@ describe('download event', () => {
await page.close();
});
it('should error when saving with downloads disabled', async ({testInfo, browser, server}) => {
it('should error when saving with downloads disabled', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: false });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -187,7 +186,7 @@ describe('download event', () => {
await page.close();
});
it('should error when saving after deletion', async ({testInfo, browser, server}) => {
it('should error when saving after deletion', async ({browser, server}, testInfo) => {
const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([
@ -235,6 +234,7 @@ describe('download event', () => {
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 => {
@ -248,9 +248,10 @@ describe('download event', () => {
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}) => {
it('should report alt-click downloads', async ({browser, server, browserName}) => {
it.fixme(browserName === 'firefox' || browserName === 'webkit');
// 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) => {
@ -271,9 +272,9 @@ describe('download event', () => {
await page.close();
});
it('should report new window downloads', (test, { browserName, headful }) => {
test.fixme(browserName === 'chromium' && headful);
}, async ({browser, server}) => {
it('should report new window downloads', async ({browser, server, browserName, headful}) => {
it.fixme(browserName === 'chromium' && headful);
// 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
@ -359,9 +360,9 @@ describe('download event', () => {
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
});
it('should close the context without awaiting the failed download', (test, { browserName }) => {
test.skip(browserName !== 'chromium', 'Only Chromium downloads on alt-click');
}, async ({browser, server, httpsServer, testInfo}) => {
it('should close the context without awaiting the failed download', async ({browser, server, httpsServer, browserName}, testInfo) => {
it.skip(browserName !== 'chromium', 'Only Chromium downloads on alt-click');
const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href="${httpsServer.PREFIX}/downloadWithFilename" download="file.txt">click me</a>`);
@ -380,9 +381,9 @@ describe('download event', () => {
expect(saveError.message).toContain('File deleted upon browser context closure.');
});
it('should close the context without awaiting the download', (test, { browserName, platform }) => {
test.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers');
}, async ({browser, server, testInfo}) => {
it('should close the context without awaiting the download', async ({browser, server, browserName, platform}, testInfo) => {
it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers');
server.setRoute('/downloadStall', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');

View File

@ -16,12 +16,15 @@
*/
import path from 'path';
import { it } from './fixtures';
import { test as it } from './config/browserTest';
it('should load svg favicon with prefer-color-scheme', async ({contextFactory, server, browserName, browserChannel, headful}) => {
it.skip(!headful && browserName !== 'firefox', 'headless browsers, except firefox, do not request favicons');
it.skip(headful && browserName === 'webkit' && !browserChannel, 'playwright headful webkit does not have a favicon feature');
const context = await contextFactory();
const page = await context.newPage();
it('should load svg favicon with prefer-color-scheme', (test, {browserName, browserChannel, headful}) => {
test.skip(!headful && browserName !== 'firefox', 'headless browsers, except firefox, do not request favicons');
test.skip(headful && browserName === 'webkit' && !browserChannel, 'playwright headful webkit does not have a favicon feature');
}, async ({page, server}) => {
// Browsers aggresively cache favicons, so force bust with the
// `d` parameter to make iterating on this test more predictable and isolated.
const favicon = `/favicon.svg?d=${Date.now()}`;
@ -57,4 +60,6 @@ it('should load svg favicon with prefer-color-scheme', (test, {browserName, brow
await page.waitForTimeout(500);
// Text still being around ensures we haven't actually lost our browser to a crash.
await page.waitForSelector('text=favicons');
await page.close();
});

View File

@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { it, expect } from '../fixtures';
it('should pass firefox user preferences', (test, { browserName }) => {
test.skip(browserName !== 'firefox');
}, async ({browserType, browserOptions}) => {
import { test as it, expect } from '../config/playwrightTest';
it('should pass firefox user preferences', async ({browserType, browserOptions, browserName}) => {
it.skip(browserName !== 'firefox');
const browser = await browserType.launch({
...browserOptions,
firefoxUserPrefs: {

View File

@ -55,10 +55,18 @@ function mergeEnvs(envs: any[]): any {
}
},
afterAll: async (workerInfo: WorkerInfo) => {
let error: Error | undefined;
for (const env of backward) {
if (env.afterAll)
await env.afterAll(workerInfo);
if (env.afterAll) {
try {
await env.afterAll(workerInfo);
} catch (e) {
error = error || e;
}
}
}
if (error)
throw error;
},
beforeEach: async (testInfo: TestInfo) => {
let result = undefined;
@ -71,10 +79,18 @@ function mergeEnvs(envs: any[]): any {
return result;
},
afterEach: async (testInfo: TestInfo) => {
let error: Error | undefined;
for (const env of backward) {
if (env.afterEach)
await env.afterEach(testInfo);
if (env.afterEach) {
try {
await env.afterEach(testInfo);
} catch (e) {
error = error || e;
}
}
}
if (error)
throw error;
},
};
}

View File

@ -14,28 +14,28 @@
* limitations under the License.
*/
import { Page } from '..';
import { folio } from './fixtures';
import { recorderPageGetter } from './utils';
const { afterEach, it, describe, expect } = folio;
import { Page } from '../index';
import { test as it, expect } from './config/cliTest';
describe('pause', (suite, { mode }) => {
suite.skip(mode !== 'default');
}, () => {
afterEach(async ({ context, toImpl }) => {
it.describe('pause', () => {
it.beforeEach(async ({ mode }) => {
it.skip(mode !== 'default');
});
it.afterEach(async ({ recorderPageGetter }) => {
try {
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
recorderPage.click('[title=Resume]').catch(() => {});
} catch (e) {
// Some tests close context.
}
});
it('should pause and resume the script', async ({ page, context, toImpl }) => {
it('should pause and resume the script', async ({ page, recorderPageGetter }) => {
const scriptPromise = (async () => {
await page.pause();
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title=Resume]');
await scriptPromise;
});
@ -52,33 +52,33 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should pause after a navigation', async ({page, server, context, toImpl}) => {
it('should pause after a navigation', async ({page, server, recorderPageGetter}) => {
const scriptPromise = (async () => {
await page.goto(server.EMPTY_PAGE);
await page.pause();
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title=Resume]');
await scriptPromise;
});
it('should show source', async ({page, context, toImpl}) => {
it('should show source', async ({page, recorderPageGetter}) => {
const scriptPromise = (async () => {
await page.pause();
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
const source = await recorderPage.textContent('.source-line-paused .source-code');
expect(source).toContain('page.pause()');
await recorderPage.click('[title=Resume]');
await scriptPromise;
});
it('should pause on next pause', async ({page, context, toImpl}) => {
it('should pause on next pause', async ({page, recorderPageGetter}) => {
const scriptPromise = (async () => {
await page.pause(); // 1
await page.pause(); // 2
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
const source = await recorderPage.textContent('.source-line-paused');
expect(source).toContain('page.pause(); // 1');
await recorderPage.click('[title=Resume]');
@ -87,13 +87,13 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should step', async ({page, context, toImpl}) => {
it('should step', async ({page, recorderPageGetter}) => {
await page.setContent('<button>Submit</button>');
const scriptPromise = (async () => {
await page.pause();
await page.click('button');
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
const source = await recorderPage.textContent('.source-line-paused');
expect(source).toContain('page.pause();');
@ -104,13 +104,13 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should highlight pointer', async ({page, context, toImpl}) => {
it('should highlight pointer', async ({page, recorderPageGetter}) => {
await page.setContent('<button>Submit</button>');
const scriptPromise = (async () => {
await page.pause();
await page.click('button');
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Step over"]');
const point = await page.waitForSelector('x-pw-action-point');
@ -130,28 +130,28 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should skip input when resuming', async ({page, context, toImpl}) => {
it('should skip input when resuming', async ({page, recorderPageGetter}) => {
await page.setContent('<button>Submit</button>');
const scriptPromise = (async () => {
await page.pause();
await page.click('button');
await page.pause(); // 2
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
await recorderPage.click('[title=Resume]');
await scriptPromise;
});
it('should populate log', async ({page, context, toImpl}) => {
it('should populate log', async ({page, recorderPageGetter}) => {
await page.setContent('<button>Submit</button>');
const scriptPromise = (async () => {
await page.pause();
await page.click('button');
await page.pause(); // 2
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
expect(await sanitizeLog(recorderPage)).toEqual([
@ -163,7 +163,7 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should highlight waitForEvent', async ({page, context, toImpl}) => {
it('should highlight waitForEvent', async ({page, recorderPageGetter}) => {
await page.setContent('<button onclick="console.log(1)">Submit</button>');
const scriptPromise = (async () => {
await page.pause();
@ -172,7 +172,7 @@ describe('pause', (suite, { mode }) => {
page.click('button'),
]);
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Step over"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.click")');
await recorderPage.waitForSelector('.source-line-running:has-text("page.waitForEvent")');
@ -180,7 +180,7 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should populate log with waitForEvent', async ({page, context, toImpl}) => {
it('should populate log with waitForEvent', async ({page, recorderPageGetter}) => {
await page.setContent('<button onclick="console.log(1)">Submit</button>');
const scriptPromise = (async () => {
await page.pause();
@ -190,7 +190,7 @@ describe('pause', (suite, { mode }) => {
]);
await page.pause(); // 2
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause(); // 2")');
expect(await sanitizeLog(recorderPage)).toEqual([
@ -203,13 +203,13 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should populate log with error', async ({page, context, toImpl}) => {
it('should populate log with error', async ({page, recorderPageGetter}) => {
await page.setContent('<button onclick="console.log(1)">Submit</button>');
const scriptPromise = (async () => {
await page.pause();
await page.isChecked('button');
})().catch(e => e);
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Resume"]');
await recorderPage.waitForSelector('.source-line-error');
expect(await sanitizeLog(recorderPage)).toEqual([
@ -223,7 +223,7 @@ describe('pause', (suite, { mode }) => {
expect(error.message).toContain('Not a checkbox or radio button');
});
it('should populate log with error in waitForEvent', async ({page, context, toImpl}) => {
it('should populate log with error in waitForEvent', async ({page, recorderPageGetter}) => {
await page.setContent('<button>Submit</button>');
const scriptPromise = (async () => {
await page.pause();
@ -232,7 +232,7 @@ describe('pause', (suite, { mode }) => {
page.click('button'),
]);
})().catch(() => {});
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Step over"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.click")');
await recorderPage.waitForSelector('.source-line-error:has-text("page.waitForEvent")');
@ -247,36 +247,36 @@ describe('pause', (suite, { mode }) => {
await scriptPromise;
});
it('should pause on page close', async ({ page, context, toImpl }) => {
it('should pause on page close', async ({ page, recorderPageGetter }) => {
const scriptPromise = (async () => {
await page.pause();
await page.close();
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Step over"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.close();")');
await recorderPage.click('[title=Resume]');
await scriptPromise;
});
it('should pause on context close', async ({ page, context, toImpl }) => {
it('should pause on context close', async ({ page, recorderPageGetter }) => {
const scriptPromise = (async () => {
await page.pause();
await page.context().close();
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
await recorderPage.click('[title="Step over"]');
await recorderPage.waitForSelector('.source-line-paused:has-text("page.context().close();")');
await recorderPage.click('[title=Resume]');
await scriptPromise;
});
it('should highlight on explore', async ({ page, context, toImpl }) => {
it('should highlight on explore', async ({ page, recorderPageGetter }) => {
await page.setContent('<button>Submit</button>');
const scriptPromise = (async () => {
await page.pause();
})();
const recorderPage = await recorderPageGetter(context, toImpl);
const recorderPage = await recorderPageGetter();
const [element] = await Promise.all([
page.waitForSelector('x-pw-highlight:visible'),
recorderPage.fill('input[placeholder="Playwright Selector"]', 'text=Submit'),

View File

@ -15,74 +15,102 @@
* limitations under the License.
*/
import { it, expect, describe } from './fixtures';
import { test as it, expect } from './config/browserTest';
function getPermission(page, name) {
return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name);
}
describe('permissions', (suite, { browserName }) => {
suite.skip(browserName === 'webkit');
}, () => {
it('should be prompt by default', async ({page, server, context}) => {
// Permissions API is not implemented in WebKit (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('prompt');
it.describe('permissions', () => {
it.beforeEach(async ({ browserName }) => {
it.skip(browserName === 'webkit', 'Permissions API is not implemented in WebKit (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)');
});
it('should deny permission when not listed', async ({page, server, context}) => {
it('should be prompt by default', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'geolocation')).toBe('prompt');
await context.close();
});
it('should deny permission when not listed', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied');
await context.close();
});
it('should fail when bad permission is given', async ({page, server, context}) => {
it('should fail when bad permission is given', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
let error: Error;
await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e);
expect(error.message).toContain('Unknown permission: foo');
await context.close();
});
it('should grant geolocation permission when origin is listed', async ({page, server, context}) => {
it('should grant geolocation permission when origin is listed', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.close();
});
it('should prompt for geolocation permission when origin is not listed', async ({page, server, context}) => {
it('should prompt for geolocation permission when origin is not listed', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
await page.goto(server.EMPTY_PAGE.replace('localhost', '127.0.0.1'));
expect(await getPermission(page, 'geolocation')).toBe('prompt');
await context.close();
});
it('should grant notifications permission when listed', async ({page, server, context}) => {
it('should grant notifications permission when listed', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'notifications')).toBe('granted');
await context.close();
});
it('should accumulate when adding', async ({page, server, context}) => {
it('should accumulate when adding', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
await context.close();
});
it('should clear permissions', async ({page, server, context}) => {
it('should clear permissions', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
await context.clearPermissions();
await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).not.toBe('granted');
expect(await getPermission(page, 'notifications')).toBe('granted');
await context.close();
});
it('should grant permission when listed for all domains', async ({page, server, context}) => {
it('should grant permission when listed for all domains', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.close();
});
it('should grant permission when creating context', async ({server, browser}) => {
@ -93,21 +121,23 @@ describe('permissions', (suite, { browserName }) => {
await context.close();
});
it('should reset permissions', async ({page, server, context}) => {
it('should reset permissions', async ({contextFactory, server}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt');
await context.close();
});
it('should trigger permission onchange', (test, { browserName, headful, platform }) => {
test.fail(browserName === 'webkit');
test.fail(browserName === 'chromium' && headful);
}, async ({page, server, context}) => {
// TODO: flaky
// - Linux: https://github.com/microsoft/playwright/pull/1790/checks?check_run_id=587327883
// - Win: https://ci.appveyor.com/project/aslushnikov/playwright/builds/32402536
it('should trigger permission onchange', async ({contextFactory, server, browserName, headful}) => {
it.fail(browserName === 'webkit');
it.fail(browserName === 'chromium' && headful);
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
window['events'] = [];
@ -125,9 +155,12 @@ describe('permissions', (suite, { browserName }) => {
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']);
await context.clearPermissions();
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']);
await context.close();
});
it('should isolate permissions between browser contexts', async ({page, server, context, browser}) => {
it('should isolate permissions between browser contexts', async ({server, browser}) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const otherContext = await browser.newContext();
const otherPage = await otherContext.newPage();
@ -144,13 +177,16 @@ describe('permissions', (suite, { browserName }) => {
expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await otherContext.close();
await context.close();
});
it('should support clipboard read', (test, { browserName, headful }) => {
test.fail(browserName === 'webkit');
test.fail(browserName === 'firefox', 'No such permissions (requires flag) in Firefox');
test.fixme(browserName === 'chromium' && headful);
}, async ({page, server, context}) => {
it('should support clipboard read', async ({contextFactory, server, browserName, headful}) => {
it.fail(browserName === 'webkit');
it.fail(browserName === 'firefox', 'No such permissions (requires flag) in Firefox');
it.fixme(browserName === 'chromium' && headful);
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'clipboard-read')).toBe('prompt');
let error;
@ -159,5 +195,6 @@ describe('permissions', (suite, { browserName }) => {
await context.grantPermissions(['clipboard-read']);
expect(await getPermission(page, 'clipboard-read')).toBe('granted');
await page.evaluate(() => navigator.clipboard.readText());
await context.close();
});
});

View File

@ -15,9 +15,11 @@
* limitations under the License.
*/
import { expect, it } from './fixtures';
import { test as it, expect } from './config/browserTest';
it('should work', async ({ page, server }) => {
it('should work', async ({ contextFactory, server }) => {
const context = await contextFactory();
const page = await context.newPage();
const [request] = await Promise.all([
page.waitForEvent('requestfinished'),
page.goto(server.EMPTY_PAGE)
@ -28,9 +30,12 @@ it('should work', async ({ page, server }) => {
expect(timing.responseStart).toBeGreaterThanOrEqual(timing.requestStart);
expect(timing.responseEnd).toBeGreaterThanOrEqual(timing.responseStart);
expect(timing.responseEnd).toBeLessThan(10000);
await context.close();
});
it('should work for subresource', async ({ page, server }) => {
it('should work for subresource', async ({ contextFactory, server }) => {
const context = await contextFactory();
const page = await context.newPage();
const requests = [];
page.on('requestfinished', request => requests.push(request));
await page.goto(server.PREFIX + '/one-style.html');
@ -41,6 +46,7 @@ it('should work for subresource', async ({ page, server }) => {
expect(timing.responseStart).toBeGreaterThan(timing.requestStart);
expect(timing.responseEnd).toBeGreaterThanOrEqual(timing.responseStart);
expect(timing.responseEnd).toBeLessThan(10000);
await context.close();
});
it('should work for SSL', async ({ browser, httpsServer }) => {
@ -58,9 +64,11 @@ it('should work for SSL', async ({ browser, httpsServer }) => {
await page.close();
});
it('should work for redirect', (test, { browserName }) => {
test.fixme(browserName === 'webkit', `In WebKit, redirects don't carry the timing info`);
}, async ({ page, server }) => {
it('should work for redirect', async ({ contextFactory, browserName, server }) => {
it.fixme(browserName === 'webkit', `In WebKit, redirects don't carry the timing info`);
const context = await contextFactory();
const page = await context.newPage();
server.setRedirect('/foo.html', '/empty.html');
const responses = [];
page.on('response', response => responses.push(response));
@ -84,6 +92,8 @@ it('should work for redirect', (test, { browserName }) => {
expect(timing2.responseStart).toBeGreaterThan(timing2.requestStart);
expect(timing2.responseEnd).toBeGreaterThanOrEqual(timing2.responseStart);
expect(timing2.responseEnd).toBeLessThan(10000);
await context.close();
});
function verifyTimingValue(value: number, previous: number) {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { it, expect, describe } from './fixtures';
import { slowTest as it, expect } from './config/browserTest';
import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
@ -151,15 +151,13 @@ function expectRedFrames(videoFile: string, size: { width: number, height: numbe
}
}
describe('screencast', suite => {
suite.slow();
}, () => {
it.describe('screencast', () => {
it('videoSize should require videosPath', async ({browser}) => {
const error = await browser.newContext({ videoSize: { width: 100, height: 100 } }).catch(e => e);
expect(error.message).toContain('"videoSize" option requires "videosPath" to be specified');
});
it('should work with old options', async ({browser, testInfo}) => {
it('should work with old options', async ({browser}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 450, height: 240 };
const context = await browser.newContext({
@ -182,7 +180,7 @@ describe('screencast', suite => {
expect(error.message).toContain('recordVideo.dir: expected string, got undefined');
});
it('should capture static page', async ({browser, testInfo}) => {
it('should capture static page', async ({browser}, testInfo) => {
const size = { width: 450, height: 240 };
const context = await browser.newContext({
recordVideo: {
@ -201,7 +199,7 @@ describe('screencast', suite => {
expectRedFrames(videoFile, size);
});
it('should expose video path', async ({browser, testInfo}) => {
it('should expose video path', async ({browser}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
@ -219,7 +217,7 @@ describe('screencast', suite => {
expect(fs.existsSync(path)).toBeTruthy();
});
it('should saveAs video', async ({browser, testInfo}) => {
it('should saveAs video', async ({browser}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
@ -239,7 +237,7 @@ describe('screencast', suite => {
expect(fs.existsSync(saveAsPath)).toBeTruthy();
});
it('saveAs should throw when no video frames', async ({browser, browserName, testInfo}) => {
it('saveAs should throw when no video frames', async ({browser, browserName}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
@ -269,7 +267,7 @@ describe('screencast', suite => {
expect(error.message).toContain('Page did not produce any video frames');
});
it('should delete video', async ({browser, testInfo}) => {
it('should delete video', async ({browser}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
@ -290,7 +288,7 @@ describe('screencast', suite => {
expect(fs.existsSync(videoPath)).toBeFalsy();
});
it('should expose video path blank page', async ({browser, testInfo}) => {
it('should expose video path blank page', async ({browser}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
@ -307,7 +305,7 @@ describe('screencast', suite => {
expect(fs.existsSync(path)).toBeTruthy();
});
it('should expose video path blank popup', async ({browser, testInfo}) => {
it('should expose video path blank popup', async ({browser}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
@ -328,7 +326,7 @@ describe('screencast', suite => {
expect(fs.existsSync(path)).toBeTruthy();
});
it('should capture navigation', async ({browser, server, testInfo}) => {
it('should capture navigation', async ({browser, server}, testInfo) => {
const context = await browser.newContext({
recordVideo: {
dir: testInfo.outputPath(''),
@ -359,10 +357,10 @@ describe('screencast', suite => {
}
});
it('should capture css transformation', (test, { headful, browserName, platform }) => {
test.fixme(headful, 'Fails on headful');
test.fixme(browserName === 'webkit' && platform === 'win32', 'Fails on headful');
}, async ({browser, server, testInfo}) => {
it('should capture css transformation', async ({browser, server, headful, browserName, platform}, testInfo) => {
it.fixme(headful, 'Fails on headful');
it.fixme(browserName === 'webkit' && platform === 'win32', 'Fails on headful');
const size = { width: 320, height: 240 };
// Set viewport equal to screencast frame size to avoid scaling.
const context = await browser.newContext({
@ -389,7 +387,7 @@ describe('screencast', suite => {
}
});
it('should work for popups', async ({browser, testInfo, server}) => {
it('should work for popups', async ({browser, server}, testInfo) => {
const videosPath = testInfo.outputPath('');
const size = { width: 450, height: 240 };
const context = await browser.newContext({
@ -419,9 +417,9 @@ describe('screencast', suite => {
expect(videoFiles.length).toBe(2);
});
it('should scale frames down to the requested size ', (test, parameters) => {
test.fixme(parameters.headful, 'Fails on headful');
}, async ({browser, testInfo, server}) => {
it('should scale frames down to the requested size ', async ({browser, server, headful}, testInfo) => {
it.fixme(headful, 'Fails on headful');
const context = await browser.newContext({
recordVideo: {
dir: testInfo.outputPath(''),
@ -467,7 +465,7 @@ describe('screencast', suite => {
}
});
it('should use viewport scaled down to fit into 800x800 as default size', async ({browser, testInfo}) => {
it('should use viewport scaled down to fit into 800x800 as default size', async ({browser}, testInfo) => {
const size = {width: 1600, height: 1200};
const context = await browser.newContext({
recordVideo: {
@ -486,7 +484,7 @@ describe('screencast', suite => {
expect(videoPlayer.videoHeight).toBe(600);
});
it('should be 800x450 by default', async ({ browser, testInfo }) => {
it('should be 800x450 by default', async ({ browser }, testInfo) => {
const context = await browser.newContext({
recordVideo: {
dir: testInfo.outputPath(''),
@ -503,9 +501,9 @@ describe('screencast', suite => {
expect(videoPlayer.videoHeight).toBe(450);
});
it('should be 800x600 with null viewport', (test, { headful, browserName }) => {
test.fixme(browserName === 'firefox' && !headful, 'Fails in headless on bots');
}, async ({ browser, testInfo }) => {
it('should be 800x600 with null viewport', async ({ browser, headful, browserName }, testInfo) => {
it.fixme(browserName === 'firefox' && !headful, 'Fails in headless on bots');
const context = await browser.newContext({
recordVideo: {
dir: testInfo.outputPath(''),
@ -523,7 +521,7 @@ describe('screencast', suite => {
expect(videoPlayer.videoHeight).toBe(600);
});
it('should capture static page in persistent context', async ({launchPersistent, testInfo}) => {
it('should capture static page in persistent context', async ({launchPersistent}, testInfo) => {
const size = { width: 320, height: 240 };
const { context, page } = await launchPersistent({
recordVideo: {
@ -551,9 +549,9 @@ describe('screencast', suite => {
}
});
it('should emulate an iphone', (test, { browserName }) => {
test.skip(browserName === 'firefox', 'isMobile is not supported in Firefox');
}, async ({contextFactory, playwright, contextOptions, testInfo}) => {
it('should emulate an iphone', async ({contextFactory, playwright, contextOptions, browserName}, testInfo) => {
it.skip(browserName === 'firefox', 'isMobile is not supported in Firefox');
const device = playwright.devices['iPhone 6'];
const context = await contextFactory({
...contextOptions,

View File

@ -14,40 +14,45 @@
* limitations under the License.
*/
import { folio } from './fixtures';
import { test as it, expect } from './config/browserTest';
import { InMemorySnapshotter } from '../lib/server/snapshot/inMemorySnapshotter';
import { HttpServer } from '../lib/utils/httpServer';
import { SnapshotServer } from '../lib/server/snapshot/snapshotServer';
const { it, describe, expect, beforeEach, afterEach } = folio;
import type { BrowserContext, Page } from '../index';
describe('snapshots', (suite, { mode }) => {
suite.skip(mode !== 'default');
}, () => {
it.describe('snapshots', () => {
let context: BrowserContext;
let page: Page;
let snapshotter: any;
let httpServer: any;
let snapshotPort: number;
beforeEach(async ({ context, toImpl, testWorkerIndex }) => {
it.beforeEach(async ({ mode, toImpl, contextFactory }, testInfo) => {
it.skip(mode !== 'default');
context = await contextFactory();
page = await context.newPage();
snapshotter = new InMemorySnapshotter(toImpl(context));
await snapshotter.initialize();
httpServer = new HttpServer();
new SnapshotServer(httpServer, snapshotter);
snapshotPort = 9700 + testWorkerIndex;
snapshotPort = 9700 + testInfo.workerIndex;
httpServer.start(snapshotPort);
});
afterEach(async () => {
it.afterEach(async () => {
await snapshotter.dispose();
httpServer.stop();
await context.close();
});
it('should collect snapshot', async ({ page, toImpl }) => {
it('should collect snapshot', async ({ toImpl }) => {
await page.setContent('<button>Hello</button>');
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot');
expect(distillSnapshot(snapshot)).toBe('<BUTTON>Hello</BUTTON>');
});
it('should capture resources', async ({ page, toImpl, server }) => {
it('should capture resources', async ({ toImpl, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.route('**/style.css', route => {
route.fulfill({ body: 'button { color: red; }', }).catch(() => {});
@ -59,7 +64,7 @@ describe('snapshots', (suite, { mode }) => {
expect(resources[cssHref]).toBeTruthy();
});
it('should collect multiple', async ({ page, toImpl }) => {
it('should collect multiple', async ({ toImpl }) => {
await page.setContent('<button>Hello</button>');
const snapshots = [];
snapshotter.on('snapshot', snapshot => snapshots.push(snapshot));
@ -68,7 +73,7 @@ describe('snapshots', (suite, { mode }) => {
expect(snapshots.length).toBe(2);
});
it('should only collect on change', async ({ page }) => {
it('should only collect on change', async ({}) => {
await page.setContent('<button>Hello</button>');
const snapshots = [];
snapshotter.on('snapshot', snapshot => snapshots.push(snapshot));
@ -83,7 +88,7 @@ describe('snapshots', (suite, { mode }) => {
expect(snapshots.length).toBe(2);
});
it('should respect inline CSSOM change', async ({ page }) => {
it('should respect inline CSSOM change', async ({}) => {
await page.setContent('<style>button { color: red; }</style><button>Hello</button>');
const snapshots = [];
snapshotter.on('snapshot', snapshot => snapshots.push(snapshot));
@ -102,7 +107,7 @@ describe('snapshots', (suite, { mode }) => {
expect(distillSnapshot(snapshots[1])).toBe('<style>button { color: blue; }</style><BUTTON>Hello</BUTTON>');
});
it('should respect subresource CSSOM change', async ({ page, server }) => {
it('should respect subresource CSSOM change', async ({ server }) => {
await page.goto(server.EMPTY_PAGE);
await page.route('**/style.css', route => {
route.fulfill({ body: 'button { color: red; }', }).catch(() => {});
@ -129,9 +134,9 @@ describe('snapshots', (suite, { mode }) => {
expect(snapshotter.resourceContent(sha1).toString()).toBe('button { color: blue; }');
});
it('should capture iframe', (test, { browserName }) => {
test.skip(browserName === 'firefox');
}, async ({ contextFactory, page, server, toImpl }) => {
it('should capture iframe', async ({ contextFactory, server, toImpl, browserName }) => {
it.skip(browserName === 'firefox');
await page.route('**/empty.html', route => {
route.fulfill({
body: '<iframe src="iframe.html"></iframe>',
@ -170,7 +175,7 @@ describe('snapshots', (suite, { mode }) => {
expect(await button.textContent()).toBe('Hello iframe');
});
it('should capture snapshot target', async ({ page, toImpl }) => {
it('should capture snapshot target', async ({ toImpl }) => {
await page.setContent('<button>Hello</button><button>World</button>');
{
const handle = await page.$('text=Hello');
@ -184,7 +189,7 @@ describe('snapshots', (suite, { mode }) => {
}
});
it('should collect on attribute change', async ({ page, toImpl }) => {
it('should collect on attribute change', async ({ toImpl }) => {
await page.setContent('<button>Hello</button>');
{
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'snapshot');

View File

@ -13,21 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { expect, folio } from './fixtures';
import { ElementHandle } from '..';
import { test as it, expect } from './config/browserTest';
import { ElementHandle, Page } from '../index';
import type { ServerResponse } from 'http';
const fixtures = folio.extend();
fixtures.page.override(async ({browser}, runTest) => {
const page = await browser.newPage({
let page: Page;
it.beforeEach(async ({browser}) => {
page = await browser.newPage({
hasTouch: true
});
await runTest(page);
});
it.afterEach(async () => {
await page.close();
});
const { it } = fixtures.build();
it('should send all of the correct events', async ({page}) => {
it('should send all of the correct events', async ({}) => {
await page.setContent(`
<div id="a" style="background: lightblue; width: 50px; height: 50px">a</div>
<div id="b" style="background: pink; width: 50px; height: 50px">b</div>
@ -47,7 +50,7 @@ it('should send all of the correct events', async ({page}) => {
]);
});
it('should not send mouse events touchstart is canceled', async ({page}) => {
it('should not send mouse events touchstart is canceled', async ({}) => {
await page.setContent(`<div style="width: 50px; height: 50px; background: red">`);
await page.evaluate(() => {
// touchstart is not cancelable unless passive is false
@ -63,7 +66,7 @@ it('should not send mouse events touchstart is canceled', async ({page}) => {
]);
});
it('should not send mouse events when touchend is canceled', async ({page}) => {
it('should not send mouse events when touchend is canceled', async ({}) => {
await page.setContent(`<div style="width: 50px; height: 50px; background: red">`);
await page.evaluate(() => {
document.addEventListener('touchend', t => t.preventDefault());
@ -78,7 +81,7 @@ it('should not send mouse events when touchend is canceled', async ({page}) => {
]);
});
it('should wait for a navigation caused by a tap', async ({server, page}) => {
it('should wait for a navigation caused by a tap', async ({server}) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<a href="/intercept-this.html">link</a>;
@ -101,7 +104,7 @@ it('should wait for a navigation caused by a tap', async ({server, page}) => {
expect(resolved).toBe(true);
});
it('should work with modifiers', async ({page}) => {
it('should work with modifiers', async ({}) => {
await page.setContent('hello world');
const altKeyPromise = page.evaluate(() => new Promise(resolve => {
document.addEventListener('touchstart', event => {
@ -116,7 +119,7 @@ it('should work with modifiers', async ({page}) => {
expect(await altKeyPromise).toBe(true);
});
it('should send well formed touch points', async ({page}) => {
it('should send well formed touch points', async ({}) => {
const promises = Promise.all([
page.evaluate(() => new Promise(resolve => {
document.addEventListener('touchstart', event => {
@ -168,7 +171,7 @@ it('should send well formed touch points', async ({page}) => {
expect(touchend).toEqual([]);
});
it('should wait until an element is visible to tap it', async ({page}) => {
it('should wait until an element is visible to tap it', async ({}) => {
const div = await page.evaluateHandle(() => {
const button = document.createElement('button');
button.textContent = 'not clicked';

View File

@ -15,7 +15,12 @@
* limitations under the License.
*/
import { it, expect } from './fixtures';
import { test as it, expect } from './config/pageTest';
import { Server as WebSocketServer } from 'ws';
it.beforeEach(async () => {
it.skip(!!process.env.PW_ANDROID_TESTS);
});
it('should work', async ({ page, server }) => {
const value = await page.evaluate(port => {
@ -47,7 +52,7 @@ it('should emit close events', async ({ page, server }) => {
expect(webSocket.isClosed()).toBeTruthy();
});
it('should emit frame events', async ({ page, server, isFirefox }) => {
it('should emit frame events', async ({ page, server }) => {
let socketClosed;
const socketClosePromise = new Promise(f => socketClosed = f);
const log = [];
@ -69,7 +74,7 @@ it('should emit frame events', async ({ page, server, isFirefox }) => {
expect(log.join(':')).toBe('close:open:received<incoming>:sent<outgoing>');
});
it('should pass self as argument to close event', async ({ page, server, isFirefox }) => {
it('should pass self as argument to close event', async ({ page, server }) => {
let socketClosed;
const socketClosePromise = new Promise(f => socketClosed = f);
let webSocket;
@ -170,9 +175,10 @@ it('should reject waitForEvent on page close', async ({page, server}) => {
expect((await error).message).toContain('Page closed');
});
it('should turn off when offline', test => {
test.fixme();
}, async ({page, webSocketServer}) => {
it('should turn off when offline', async ({page}) => {
it.fixme();
const webSocketServer = new WebSocketServer();
const address = webSocketServer.address();
const [socket, wsHandle] = await Promise.all([
new Promise<import('ws')>(x => webSocketServer.once('connection', x)),
@ -195,4 +201,5 @@ it('should turn off when offline', test => {
await page.context().setOffline(true);
await wsHandle.evaluate(ws => ws.send('if this arrives it failed'));
expect(await result).toBe('successfully closed');
await new Promise(x => webSocketServer.close(x));
});