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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const {installCoverageHooks} = require('./coverage'); const {installCoverageHooks} = require('./coverage');
const browserName = process.env.BROWSER || 'chromium'; const browserName = process.argv[2] || 'chromium';
let api = new Set(installCoverageHooks(browserName).coverage.keys()); let api = new Set(installCoverageHooks(browserName).coverage.keys());
@ -30,8 +30,21 @@ if (browserName === 'chromium') {
if (browserName !== 'chromium') { if (browserName !== 'chromium') {
// we don't have CDPSession in non-chromium browsers // 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.send');
api.delete('cDPSession.detach'); 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 // Some permissions tests are disabled in webkit. See permissions.jest.js

View file

@ -15,8 +15,7 @@
*/ */
import { expect } from './fixtures'; import { expect } from './fixtures';
import type { Frame, Page, BrowserContext } from '../index'; import type { Frame, Page } from '../index';
import { chromium } from '../index';
export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> { export async function attachFrame(page: Page, frameId: string, url: string): Promise<Frame> {
const handle = await page.evaluateHandle(async ({ frameId, url }) => { const handle = await page.evaluateHandle(async ({ frameId, url }) => {
@ -59,12 +58,3 @@ export function expectedSSLError(browserName: string): string {
} }
return expectedSSLError; 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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'; import http from 'http';
describe('chromium', (suite, { browserName }) => { pageTest.describe('chromium', () => {
suite.skip(browserName !== 'chromium'); pageTest.beforeEach(async ({ browserName }) => {
}, () => { pageTest.skip(browserName !== 'chromium');
it('should create a worker from a service worker', async ({page, server, context}) => { pageTest.skip(!!process.env.PW_ANDROID_TESTS);
});
pageTest('should create a worker from a service worker', async ({page, server}) => {
const [worker] = await Promise.all([ const [worker] = await Promise.all([
context.waitForEvent('serviceworker'), page.context().waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html') page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]); ]);
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]'); 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([ const [worker1] = await Promise.all([
context.waitForEvent('serviceworker'), context.waitForEvent('serviceworker'),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html') page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
@ -46,31 +52,17 @@ describe('chromium', (suite, { browserName }) => {
expect(workers).toContain(worker2); 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); await page.goto(server.EMPTY_PAGE);
let serviceWorkerCreated; let serviceWorkerCreated;
context.once('serviceworker', () => serviceWorkerCreated = true); page.context().once('serviceworker', () => serviceWorkerCreated = true);
await page.evaluate(() => { await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")'); new SharedWorker('data:text/javascript,console.log("hi")');
}); });
expect(serviceWorkerCreated).not.toBeTruthy(); expect(serviceWorkerCreated).not.toBeTruthy();
}); });
it('should close service worker together with the context', async ({browser, server}) => { pageTest('Page.route should work with intervention headers', async ({server, page}) => {
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}) => {
server.setRoute('/intervention', (req, res) => res.end(` server.setRoute('/intervention', (req, res) => res.end(`
<script> <script>
document.write('<script src="${server.CROSS_PROCESS_PREFIX}/intervention.js">' + '</scr' + 'ipt>'); 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. // make it work with Edgium.
expect(serverRequest.headers.intervention).toContain('feature/5718547946799104'); expect(serverRequest.headers.intervention).toContain('feature/5718547946799104');
}); });
});
it('should connect to an existing cdp session', async ({browserType, testWorkerIndex, browserOptions }) => { playwrightTest.describe('chromium', () => {
const port = 9339 + testWorkerIndex; 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({ const browserServer = await browserType.launch({
...browserOptions, ...browserOptions,
args: ['--remote-debugging-port=' + port] 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 }) => { playwrightTest('should connect to an existing cdp session twice', async ({ browserType, browserOptions, server }, testInfo) => {
const port = 9339 + testWorkerIndex; const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({ const browserServer = await browserType.launch({
...browserOptions, ...browserOptions,
args: ['--remote-debugging-port=' + port] 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}) => { playwrightTest('should connect to existing service workers', async ({browserType, browserOptions, server}, testInfo) => {
const port = 9339 + testWorkerIndex; const port = 9339 + testInfo.workerIndex;
const browserServer = await browserType.launch({ const browserServer = await browserType.launch({
...browserOptions, ...browserOptions,
args: ['--remote-debugging-port=' + port] args: ['--remote-debugging-port=' + port]

View file

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

View file

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

View file

@ -14,30 +14,41 @@
* limitations under the License. * 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(); it.describe('oopif', () => {
fixtures.browser.override(async ({browserType, browserOptions}, run) => { let browser: Browser;
const browser = await browserType.launch({ let page: Page;
...browserOptions,
args: (browserOptions.args || []).concat(['--site-per-process']) 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 }) => { it.afterEach(async () => {
suite.skip(browserName !== 'chromium'); await page.close();
}, () => { });
it('should report oopif frames', async function({browser, page, server}) {
it.afterAll(async () => {
await browser.close();
});
it('should report oopif frames', async function({server}) {
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(await page.frames()[1].evaluate(() => '' + location.href)).toBe(server.CROSS_PROCESS_PREFIX + '/grid.html'); 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'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
@ -50,7 +61,7 @@ describe('oopif', (suite, { browserName }) => {
expect(detachedFrame).toBe(frame); 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'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
@ -69,10 +80,9 @@ describe('oopif', (suite, { browserName }) => {
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
}); });
it('should get the proper viewport', (test, { browserName }) => { it('should get the proper viewport', async ({server}) => {
test.fixme(browserName === 'chromium'); it.fixme();
test.skip(browserName !== 'chromium');
}, async ({browser, page, server}) => {
expect(page.viewportSize()).toEqual({width: 1280, height: 720}); expect(page.viewportSize()).toEqual({width: 1280, height: 720});
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
@ -91,7 +101,7 @@ describe('oopif', (suite, { browserName }) => {
expect(await oopif.evaluate(() => 'ontouchstart' in window)).toBe(false); 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'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
@ -103,7 +113,7 @@ describe('oopif', (suite, { browserName }) => {
expect(result).toBe(36); 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'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1); 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); 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'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
const oopif = page.frames()[1]; const oopif = page.frames()[1];
expect(await oopif.evaluate(() => navigator.onLine)).toBe(true); 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); 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 iPhone = playwright.devices['iPhone 6'];
const context = await browser.newContext({ ...iPhone, timezoneId: 'America/Jamaica', locale: 'fr-CH', userAgent: 'UA' }); const context = await browser.newContext({ ...iPhone, timezoneId: 'America/Jamaica', locale: 'fr-CH', userAgent: 'UA' });
const page = await context.newPage(); const page = await context.newPage();
@ -145,7 +155,7 @@ describe('oopif', (suite, { browserName }) => {
await context.close(); await context.close();
}); });
it('should respect route', async ({browser, page, server}) => { it('should respect route', async ({server}) => {
let intercepted = false; let intercepted = false;
await page.route('**/digits/0.png', route => { await page.route('**/digits/0.png', route => {
intercepted = true; intercepted = true;
@ -157,7 +167,7 @@ describe('oopif', (suite, { browserName }) => {
expect(intercepted).toBe(true); 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.setViewportSize({width: 500, height: 500});
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(page.frames().length).toBe(2); 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 }); 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.route('**/*', route => route.continue());
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); 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 = []; const requestFrames = [];
page.on('request', r => requestFrames.push(r.frame())); page.on('request', r => requestFrames.push(r.frame()));
const finishedFrames = []; const finishedFrames = [];
@ -209,8 +219,8 @@ describe('oopif', (suite, { browserName }) => {
expect(finishedFrames[2]).toBe(grandChild); expect(finishedFrames[2]).toBe(grandChild);
}); });
it('should support exposeFunction', async function({browser, context, page, server}) { it('should support exposeFunction', async function({server}) {
await context.exposeFunction('dec', a => a - 1); await page.context().exposeFunction('dec', a => a - 1);
await page.exposeFunction('inc', a => a + 1); await page.exposeFunction('inc', a => a + 1);
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); 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); expect(await page.frames()[1].evaluate(() => window['dec'](4))).toBe(3);
}); });
it('should support addInitScript', async function({browser, context, page, server}) { it('should support addInitScript', async function({server}) {
await context.addInitScript(() => window['bar'] = 17); await page.context().addInitScript(() => window['bar'] = 17);
await page.addInitScript(() => window['foo'] = 42); await page.addInitScript(() => window['foo'] = 42);
await page.goto(server.PREFIX + '/dynamic-oopif.html'); await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
@ -233,7 +243,7 @@ describe('oopif', (suite, { browserName }) => {
expect(await page.frames()[1].evaluate(() => window['bar'])).toBe(17); expect(await page.frames()[1].evaluate(() => window['bar'])).toBe(17);
}); });
// @see https://github.com/microsoft/playwright/issues/1240 // @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'); await page.goto(server.PREFIX + '/button-overlay-oopif.html');
expect(await countOOPIFs(browser)).toBe(1); expect(await countOOPIFs(browser)).toBe(1);
await page.click('button'); await page.click('button');
@ -265,7 +275,7 @@ describe('oopif', (suite, { browserName }) => {
await browser.close(); 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.goto(server.PREFIX + '/dynamic-oopif.html');
await page.$eval('iframe', iframe => { await page.$eval('iframe', iframe => {
iframe.style.width = '500px'; 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 }); 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.goto(server.PREFIX + '/dynamic-oopif.html');
await page.$eval('iframe', iframe => { await page.$eval('iframe', iframe => {
iframe.style.width = '500px'; iframe.style.width = '500px';

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@ import type { Env, TestInfo, WorkerInfo } from '../folio/out';
import { PageEnv } from './browserEnv'; import { PageEnv } from './browserEnv';
import { CLIMock, CLITestArgs, Recorder } from './cliTest'; import { CLIMock, CLITestArgs, Recorder } from './cliTest';
import * as http from 'http'; import * as http from 'http';
import { recorderPageGetter } from '../../test/utils'; import { chromium } from '../../index';
export class CLIEnv extends PageEnv implements Env<CLITestArgs> { export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
private _server: http.Server | undefined; private _server: http.Server | undefined;
@ -29,8 +29,9 @@ export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
async beforeAll(workerInfo: WorkerInfo) { async beforeAll(workerInfo: WorkerInfo) {
await super.beforeAll(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); 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[]) { private _runCLI(args: string[]) {
@ -41,6 +42,14 @@ export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
async beforeEach(testInfo: TestInfo) { async beforeEach(testInfo: TestInfo) {
const result = await super.beforeEach(testInfo); const result = await super.beforeEach(testInfo);
const { page, context, toImpl } = result; 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 { return {
...result, ...result,
httpServer: { httpServer: {
@ -50,9 +59,9 @@ export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
runCLI: this._runCLI.bind(this), runCLI: this._runCLI.bind(this),
openRecorder: async () => { openRecorder: async () => {
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true }); await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
const recorderPage = await recorderPageGetter(context, toImpl); return new Recorder(page, await recorderPageGetter());
return new Recorder(page, recorderPage);
}, },
recorderPageGetter,
}; };
} }

View file

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

View file

@ -70,7 +70,8 @@ for (const browserName of browsers) {
pageTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {}); pageTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {});
// TODO: get rid of contextTest if there isn't too many of them. // TODO: get rid of contextTest if there isn't too many of them.
contextTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {}); 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') if (browserName === 'chromium')
electronTest.runWith(browserName, serverEnv, new ElectronEnv({ mode })); 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 assetsPath = path.join(__dirname, '..', '..', 'test', 'assets');
const cachedPath = path.join(__dirname, '..', '..', 'test', 'assets', 'cached'); 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 = await TestServer.create(assetsPath, port);
this._server.enableHTTPCache(cachedPath); this._server.enableHTTPCache(cachedPath);
@ -54,7 +54,7 @@ export class ServerEnv implements Env<ServerTestArgs> {
].join('\r\n')); ].join('\r\n'));
} }
}); });
this._socksPort = 9107 + workerInfo.workerIndex * 2; this._socksPort = port + 2;
this._socksServer.listen(this._socksPort, 'localhost'); this._socksServer.listen(this._socksPort, 'localhost');
this._socksServer.useAuth(socks.auth.None()); this._socksServer.useAuth(socks.auth.None());
} }

View file

@ -14,15 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { folio } from './fixtures'; import { test as it, expect } from './config/browserTest';
const { it, expect, beforeEach, describe } = folio;
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import util from 'util'; import util from 'util';
describe('download event', () => { it.describe('download event', () => {
beforeEach(async ({server}) => { it.beforeEach(async ({server}) => {
server.setRoute('/download', (req, res) => { server.setRoute('/download', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment'); 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>`); await page.setContent(`<a href="${server.PREFIX}/downloadWithFilename">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
page.waitForEvent('download'), page.waitForEvent('download'),
@ -47,6 +46,7 @@ describe('download event', () => {
await download.path().catch(e => error = e); await download.path().catch(e => error = e);
expect(await download.failure()).toContain('acceptDownloads'); expect(await download.failure()).toContain('acceptDownloads');
expect(error.message).toContain('acceptDownloads: true'); expect(error.message).toContain('acceptDownloads: true');
await page.close();
}); });
it('should report downloads with acceptDownloads: true', async ({browser, server}) => { it('should report downloads with acceptDownloads: true', async ({browser, server}) => {
@ -62,10 +62,9 @@ describe('download event', () => {
await page.close(); await page.close();
}); });
it('should report proper download url when download is from download attribute', (test, {browserName}) => { it('should report proper download url when download is from download attribute', async ({browser, server, browserName}) => {
// @see https://github.com/microsoft/playwright/issues/5537 it.fixme(browserName === 'webkit', '@see https://github.com/microsoft/playwright/issues/5537');
test.fixme(browserName === 'webkit');
}, async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.PREFIX + '/empty.html'); await page.goto(server.PREFIX + '/empty.html');
await page.setContent(`<a href="${server.PREFIX}/chromium-linux.zip" download="foo.zip">download</a>`); 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(); 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 }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -105,7 +104,7 @@ describe('download event', () => {
await page.close(); 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 }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -123,7 +122,7 @@ describe('download event', () => {
await page.close(); 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 }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -142,7 +141,7 @@ describe('download event', () => {
await page.close(); 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 }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -160,7 +159,7 @@ describe('download event', () => {
await page.close(); 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 }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -174,7 +173,7 @@ describe('download event', () => {
await page.close(); 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 }); const page = await browser.newPage({ acceptDownloads: false });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -187,7 +186,7 @@ describe('download event', () => {
await page.close(); 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 }); const page = await browser.newPage({ acceptDownloads: true });
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`); await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [ download ] = await Promise.all([ const [ download ] = await Promise.all([
@ -235,6 +234,7 @@ describe('download event', () => {
expect(fs.readFileSync(path).toString()).toBe('Hello world'); expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it(`should report download path within page.on('download', …) handler for Blobs`, async ({browser, server}) => { it(`should report download path within page.on('download', …) handler for Blobs`, async ({browser, server}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
const onDownloadPath = new Promise<string>(res => { const onDownloadPath = new Promise<string>(res => {
@ -248,9 +248,10 @@ describe('download event', () => {
expect(fs.readFileSync(path).toString()).toBe('Hello world'); expect(fs.readFileSync(path).toString()).toBe('Hello world');
await page.close(); await page.close();
}); });
it('should report alt-click downloads', (test, { browserName }) => {
test.fixme(browserName === 'firefox' || browserName === 'webkit'); it('should report alt-click downloads', async ({browser, server, browserName}) => {
}, async ({browser, server}) => { it.fixme(browserName === 'firefox' || browserName === 'webkit');
// Firefox does not download on alt-click by default. // Firefox does not download on alt-click by default.
// Our WebKit embedder does not download on alt-click, although Safari does. // Our WebKit embedder does not download on alt-click, although Safari does.
server.setRoute('/download', (req, res) => { server.setRoute('/download', (req, res) => {
@ -271,9 +272,9 @@ describe('download event', () => {
await page.close(); await page.close();
}); });
it('should report new window downloads', (test, { browserName, headful }) => { it('should report new window downloads', async ({browser, server, browserName, headful}) => {
test.fixme(browserName === 'chromium' && headful); it.fixme(browserName === 'chromium' && headful);
}, async ({browser, server}) => {
// TODO: - the test fails in headful Chromium as the popup page gets closed along // TODO: - the test fails in headful Chromium as the popup page gets closed along
// with the session before download completed event arrives. // with the session before download completed event arrives.
// - WebKit doesn't close the popup page // - WebKit doesn't close the popup page
@ -359,9 +360,9 @@ describe('download event', () => {
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy(); expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
}); });
it('should close the context without awaiting the failed download', (test, { browserName }) => { it('should close the context without awaiting the failed download', async ({browser, server, httpsServer, browserName}, testInfo) => {
test.skip(browserName !== 'chromium', 'Only Chromium downloads on alt-click'); it.skip(browserName !== 'chromium', 'Only Chromium downloads on alt-click');
}, async ({browser, server, httpsServer, testInfo}) => {
const page = await browser.newPage({ acceptDownloads: true }); const page = await browser.newPage({ acceptDownloads: true });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href="${httpsServer.PREFIX}/downloadWithFilename" download="file.txt">click me</a>`); 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.'); expect(saveError.message).toContain('File deleted upon browser context closure.');
}); });
it('should close the context without awaiting the download', (test, { browserName, platform }) => { it('should close the context without awaiting the download', async ({browser, server, browserName, platform}, testInfo) => {
test.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers'); it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers');
}, async ({browser, server, testInfo}) => {
server.setRoute('/downloadStall', (req, res) => { server.setRoute('/downloadStall', (req, res) => {
res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); res.setHeader('Content-Disposition', 'attachment; filename=file.txt');

View file

@ -16,12 +16,15 @@
*/ */
import path from 'path'; 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 // Browsers aggresively cache favicons, so force bust with the
// `d` parameter to make iterating on this test more predictable and isolated. // `d` parameter to make iterating on this test more predictable and isolated.
const favicon = `/favicon.svg?d=${Date.now()}`; 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); await page.waitForTimeout(500);
// Text still being around ensures we haven't actually lost our browser to a crash. // Text still being around ensures we haven't actually lost our browser to a crash.
await page.waitForSelector('text=favicons'); await page.waitForSelector('text=favicons');
await page.close();
}); });

View file

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

View file

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

View file

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

View file

@ -15,74 +15,102 @@
* limitations under the License. * limitations under the License.
*/ */
import { it, expect, describe } from './fixtures'; import { test as it, expect } from './config/browserTest';
function getPermission(page, name) { function getPermission(page, name) {
return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name); return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name);
} }
describe('permissions', (suite, { browserName }) => { it.describe('permissions', () => {
suite.skip(browserName === 'webkit'); 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 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('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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions([], { origin: server.EMPTY_PAGE }); await context.grantPermissions([], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('denied'); 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); await page.goto(server.EMPTY_PAGE);
let error: Error; let error: Error;
await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e); await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e);
expect(error.message).toContain('Unknown permission: foo'); 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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted'); 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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
await page.goto(server.EMPTY_PAGE.replace('localhost', '127.0.0.1')); await page.goto(server.EMPTY_PAGE.replace('localhost', '127.0.0.1'));
expect(await getPermission(page, 'geolocation')).toBe('prompt'); 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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE }); await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'notifications')).toBe('granted'); 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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']); await context.grantPermissions(['geolocation']);
await context.grantPermissions(['notifications']); await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).toBe('granted'); expect(await getPermission(page, 'geolocation')).toBe('granted');
expect(await getPermission(page, 'notifications')).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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']); await context.grantPermissions(['geolocation']);
await context.clearPermissions(); await context.clearPermissions();
await context.grantPermissions(['notifications']); await context.grantPermissions(['notifications']);
expect(await getPermission(page, 'geolocation')).not.toBe('granted'); expect(await getPermission(page, 'geolocation')).not.toBe('granted');
expect(await getPermission(page, 'notifications')).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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation']); await context.grantPermissions(['geolocation']);
expect(await getPermission(page, 'geolocation')).toBe('granted'); expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.close();
}); });
it('should grant permission when creating context', async ({server, browser}) => { it('should grant permission when creating context', async ({server, browser}) => {
@ -93,21 +121,23 @@ describe('permissions', (suite, { browserName }) => {
await context.close(); 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 page.goto(server.EMPTY_PAGE);
await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE });
expect(await getPermission(page, 'geolocation')).toBe('granted'); expect(await getPermission(page, 'geolocation')).toBe('granted');
await context.clearPermissions(); await context.clearPermissions();
expect(await getPermission(page, 'geolocation')).toBe('prompt'); expect(await getPermission(page, 'geolocation')).toBe('prompt');
await context.close();
}); });
it('should trigger permission onchange', (test, { browserName, headful, platform }) => { it('should trigger permission onchange', async ({contextFactory, server, browserName, headful}) => {
test.fail(browserName === 'webkit'); it.fail(browserName === 'webkit');
test.fail(browserName === 'chromium' && headful); it.fail(browserName === 'chromium' && headful);
}, async ({page, server, context}) => {
// TODO: flaky const context = await contextFactory();
// - Linux: https://github.com/microsoft/playwright/pull/1790/checks?check_run_id=587327883 const page = await context.newPage();
// - Win: https://ci.appveyor.com/project/aslushnikov/playwright/builds/32402536
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => { await page.evaluate(() => {
window['events'] = []; window['events'] = [];
@ -125,9 +155,12 @@ describe('permissions', (suite, { browserName }) => {
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']); expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']);
await context.clearPermissions(); await context.clearPermissions();
expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']); 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); await page.goto(server.EMPTY_PAGE);
const otherContext = await browser.newContext(); const otherContext = await browser.newContext();
const otherPage = await otherContext.newPage(); const otherPage = await otherContext.newPage();
@ -144,13 +177,16 @@ describe('permissions', (suite, { browserName }) => {
expect(await getPermission(page, 'geolocation')).toBe('prompt'); expect(await getPermission(page, 'geolocation')).toBe('prompt');
expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); expect(await getPermission(otherPage, 'geolocation')).toBe('granted');
await otherContext.close(); await otherContext.close();
await context.close();
}); });
it('should support clipboard read', (test, { browserName, headful }) => { it('should support clipboard read', async ({contextFactory, server, browserName, headful}) => {
test.fail(browserName === 'webkit'); it.fail(browserName === 'webkit');
test.fail(browserName === 'firefox', 'No such permissions (requires flag) in Firefox'); it.fail(browserName === 'firefox', 'No such permissions (requires flag) in Firefox');
test.fixme(browserName === 'chromium' && headful); it.fixme(browserName === 'chromium' && headful);
}, async ({page, server, context}) => {
const context = await contextFactory();
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
expect(await getPermission(page, 'clipboard-read')).toBe('prompt'); expect(await getPermission(page, 'clipboard-read')).toBe('prompt');
let error; let error;
@ -159,5 +195,6 @@ describe('permissions', (suite, { browserName }) => {
await context.grantPermissions(['clipboard-read']); await context.grantPermissions(['clipboard-read']);
expect(await getPermission(page, 'clipboard-read')).toBe('granted'); expect(await getPermission(page, 'clipboard-read')).toBe('granted');
await page.evaluate(() => navigator.clipboard.readText()); await page.evaluate(() => navigator.clipboard.readText());
await context.close();
}); });
}); });

View file

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

View file

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

View file

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

View file

@ -13,21 +13,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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'; import type { ServerResponse } from 'http';
const fixtures = folio.extend(); let page: Page;
fixtures.page.override(async ({browser}, runTest) => {
const page = await browser.newPage({ it.beforeEach(async ({browser}) => {
page = await browser.newPage({
hasTouch: true hasTouch: true
}); });
await runTest(page); });
it.afterEach(async () => {
await page.close(); 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(` await page.setContent(`
<div id="a" style="background: lightblue; width: 50px; height: 50px">a</div> <div id="a" style="background: lightblue; width: 50px; height: 50px">a</div>
<div id="b" style="background: pink; width: 50px; height: 50px">b</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.setContent(`<div style="width: 50px; height: 50px; background: red">`);
await page.evaluate(() => { await page.evaluate(() => {
// touchstart is not cancelable unless passive is false // 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.setContent(`<div style="width: 50px; height: 50px; background: red">`);
await page.evaluate(() => { await page.evaluate(() => {
document.addEventListener('touchend', t => t.preventDefault()); 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.goto(server.EMPTY_PAGE);
await page.setContent(` await page.setContent(`
<a href="/intercept-this.html">link</a>; <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); expect(resolved).toBe(true);
}); });
it('should work with modifiers', async ({page}) => { it('should work with modifiers', async ({}) => {
await page.setContent('hello world'); await page.setContent('hello world');
const altKeyPromise = page.evaluate(() => new Promise(resolve => { const altKeyPromise = page.evaluate(() => new Promise(resolve => {
document.addEventListener('touchstart', event => { document.addEventListener('touchstart', event => {
@ -116,7 +119,7 @@ it('should work with modifiers', async ({page}) => {
expect(await altKeyPromise).toBe(true); 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([ const promises = Promise.all([
page.evaluate(() => new Promise(resolve => { page.evaluate(() => new Promise(resolve => {
document.addEventListener('touchstart', event => { document.addEventListener('touchstart', event => {
@ -168,7 +171,7 @@ it('should send well formed touch points', async ({page}) => {
expect(touchend).toEqual([]); 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 div = await page.evaluateHandle(() => {
const button = document.createElement('button'); const button = document.createElement('button');
button.textContent = 'not clicked'; button.textContent = 'not clicked';

View file

@ -15,7 +15,12 @@
* limitations under the License. * 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 }) => { it('should work', async ({ page, server }) => {
const value = await page.evaluate(port => { const value = await page.evaluate(port => {
@ -47,7 +52,7 @@ it('should emit close events', async ({ page, server }) => {
expect(webSocket.isClosed()).toBeTruthy(); expect(webSocket.isClosed()).toBeTruthy();
}); });
it('should emit frame events', async ({ page, server, isFirefox }) => { it('should emit frame events', async ({ page, server }) => {
let socketClosed; let socketClosed;
const socketClosePromise = new Promise(f => socketClosed = f); const socketClosePromise = new Promise(f => socketClosed = f);
const log = []; 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>'); 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; let socketClosed;
const socketClosePromise = new Promise(f => socketClosed = f); const socketClosePromise = new Promise(f => socketClosed = f);
let webSocket; let webSocket;
@ -170,9 +175,10 @@ it('should reject waitForEvent on page close', async ({page, server}) => {
expect((await error).message).toContain('Page closed'); expect((await error).message).toContain('Page closed');
}); });
it('should turn off when offline', test => { it('should turn off when offline', async ({page}) => {
test.fixme(); it.fixme();
}, async ({page, webSocketServer}) => {
const webSocketServer = new WebSocketServer();
const address = webSocketServer.address(); const address = webSocketServer.address();
const [socket, wsHandle] = await Promise.all([ const [socket, wsHandle] = await Promise.all([
new Promise<import('ws')>(x => webSocketServer.once('connection', x)), 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 page.context().setOffline(true);
await wsHandle.evaluate(ws => ws.send('if this arrives it failed')); await wsHandle.evaluate(ws => ws.send('if this arrives it failed'));
expect(await result).toBe('successfully closed'); expect(await result).toBe('successfully closed');
await new Promise(x => webSocketServer.close(x));
}); });