Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Núria 2024-07-01 09:17:02 +02:00
commit 02a44c6921
295 changed files with 9413 additions and 6354 deletions

View file

@ -36,14 +36,23 @@ runs:
with:
node-version: ${{ inputs.node-version }}
- uses: ./.github/actions/enable-microphone-access
- run: npm ci
- run: |
echo "::group::npm ci"
npm ci
echo "::endgroup::"
shell: bash
env:
DEBUG: pw:install
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
- run: npm run build
- run: |
echo "::group::npm run build"
npm run build
echo "::endgroup::"
shell: bash
- run: npx playwright install --with-deps ${{ inputs.browsers-to-install }}
- run: |
echo "::group::npx playwright install --with-deps"
npx playwright install --with-deps ${{ inputs.browsers-to-install }}
echo "::endgroup::"
shell: bash
- name: Run tests
if: inputs.shell == 'bash'
@ -69,11 +78,15 @@ runs:
client-id: ${{ inputs.flakiness-client-id }}
tenant-id: ${{ inputs.flakiness-tenant-id }}
subscription-id: ${{ inputs.flakiness-subscription-id }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
- run: |
echo "::group::./utils/upload_flakiness_dashboard.sh"
./utils/upload_flakiness_dashboard.sh ./test-results/report.json
echo "::endgroup::"
if: ${{ !cancelled() }}
shell: bash
- name: Upload blob report
if: ${{ !cancelled() }}
# We only merge reports for PRs as per .github/workflows/create_test_report.yml.
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
uses: ./.github/actions/upload-blob-report
with:
report_dir: blob-report

View file

@ -1,7 +1,7 @@
name: Publish Test Results
on:
workflow_run:
workflows: ["tests 1", "tests 2"]
workflows: ["tests 1", "tests 2", "tests others"]
types:
- completed
jobs:

View file

@ -1,42 +0,0 @@
name: "electron"
on:
push:
branches:
- main
- release-*
pull_request:
paths-ignore:
- 'browser_patches/**'
- 'docs/**'
types: [ labeled ]
branches:
- main
- release-*
env:
# Force terminal colors. @see https://www.npmjs.com/package/colors
FORCE_COLOR: 1
jobs:
test_electron:
name: ${{ matrix.os }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
browsers-to-install: chromium
command: npm run etest
bot-name: "electron-${{ matrix.os }}"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}

161
.github/workflows/tests_others.yml vendored Normal file
View file

@ -0,0 +1,161 @@
name: tests others
on:
push:
branches:
- main
- release-*
pull_request:
paths-ignore:
- 'browser_patches/**'
- 'docs/**'
types: [ labeled ]
branches:
- main
- release-*
env:
FORCE_COLOR: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
jobs:
test_stress:
name: Stress - ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Stick with macos-latest-large for now which is Intel-based until
# https://github.com/microsoft/playwright/issues/30705 is fixed.
os: [ubuntu-latest, macos-latest-large, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: npx playwright install --with-deps
- run: npm run stest contexts -- --project=chromium
if: ${{ !cancelled() }}
- run: npm run stest browsers -- --project=chromium
if: ${{ !cancelled() }}
- run: npm run stest frames -- --project=chromium
if: ${{ !cancelled() }}
- run: npm run stest contexts -- --project=webkit
if: ${{ !cancelled() }}
- run: npm run stest browsers -- --project=webkit
if: ${{ !cancelled() }}
- run: npm run stest frames -- --project=webkit
if: ${{ !cancelled() }}
- run: npm run stest contexts -- --project=firefox
if: ${{ !cancelled() }}
- run: npm run stest browsers -- --project=firefox
if: ${{ !cancelled() }}
- run: npm run stest frames -- --project=firefox
if: ${{ !cancelled() }}
- run: npm run stest heap -- --project=chromium
if: ${{ !cancelled() }}
test_webview2:
name: WebView2
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: windows-2022
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- run: dotnet build
working-directory: tests/webview2/webview2-app/
- name: Update to Evergreen WebView2 Runtime
shell: pwsh
run: |
# See here: https://developer.microsoft.com/en-us/microsoft-edge/webview2/
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
Start-Process -FilePath setup.exe -Verb RunAs -Wait
- uses: ./.github/actions/run-test
with:
node-version: 20
browsers-to-install: chromium
command: npm run webview2test
bot-name: "webview2-chromium-windows"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
test_clock_frozen_time_linux:
name: time library - ${{ matrix.clock }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
strategy:
fail-fast: false
matrix:
clock: [frozen, realtime]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
node-version: 20
browsers-to-install: chromium
command: npm run test -- --project=chromium-*
bot-name: "${{ matrix.clock }}-time-library-chromium-linux"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
PW_CLOCK: ${{ matrix.clock }}
test_clock_frozen_time_test_runner:
name: time test runner - ${{ matrix.clock }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ubuntu-22.04
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
strategy:
fail-fast: false
matrix:
clock: [frozen, realtime]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
node-version: 20
command: npm run ttest
bot-name: "${{ matrix.clock }}-time-runner-chromium-linux"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
PW_CLOCK: ${{ matrix.clock }}
test_electron:
name: Electron - ${{ matrix.os }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
browsers-to-install: chromium
command: npm run etest
bot-name: "electron-${{ matrix.os }}"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
ELECTRON_SKIP_BINARY_DOWNLOAD:

View file

@ -31,7 +31,7 @@ jobs:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
os: [ubuntu-20.04]
os: [ubuntu-20.04, ubuntu-24.04]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
@ -116,7 +116,7 @@ jobs:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
os: [ubuntu-20.04, ubuntu-22.04, macos-14, windows-latest]
os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, macos-14, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
@ -307,7 +307,9 @@ jobs:
firefox_beta_mac:
name: "Firefox Beta (Mac)"
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: macos-latest
# Stick with macos-latest-large for now which is Intel-based until
# https://github.com/microsoft/playwright/issues/30705 is fixed.
runs-on: macos-latest-large
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test

View file

@ -1,56 +0,0 @@
name: "stress"
on:
push:
branches:
- main
- release-*
pull_request:
paths-ignore:
- 'browser_patches/**'
- 'docs/**'
types: [ labeled ]
branches:
- main
- release-*
env:
FORCE_COLOR: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
jobs:
test_components:
name: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: npx playwright install --with-deps
- run: npm run stest contexts -- --project=chromium
if: ${{ !cancelled() }}
- run: npm run stest browsers -- --project=chromium
if: ${{ !cancelled() }}
- run: npm run stest frames -- --project=chromium
if: ${{ !cancelled() }}
- run: npm run stest contexts -- --project=webkit
if: ${{ !cancelled() }}
- run: npm run stest browsers -- --project=webkit
if: ${{ !cancelled() }}
- run: npm run stest frames -- --project=webkit
if: ${{ !cancelled() }}
- run: npm run stest contexts -- --project=firefox
if: ${{ !cancelled() }}
- run: npm run stest browsers -- --project=firefox
if: ${{ !cancelled() }}
- run: npm run stest frames -- --project=firefox
if: ${{ !cancelled() }}
- run: npm run stest heap -- --project=chromium
if: ${{ !cancelled() }}

View file

@ -1,60 +0,0 @@
name: "WebView2 Tests"
on:
push:
branches:
- main
- release-*
pull_request:
paths-ignore:
- 'browser_patches/**'
- 'docs/**'
types: [ labeled ]
branches:
- main
- release-*
env:
# Force terminal colors. @see https://www.npmjs.com/package/colors
FORCE_COLOR: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
jobs:
test_webview2:
name: WebView2
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: windows-2022
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- run: npm ci
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
- run: npm run build
- run: dotnet build
working-directory: tests/webview2/webview2-app/
- name: Update to Evergreen WebView2 Runtime
shell: pwsh
run: |
# See here: https://developer.microsoft.com/en-us/microsoft-edge/webview2/
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
Start-Process -FilePath setup.exe -Verb RunAs -Wait
- run: npm run webview2test
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: ${{ !cancelled() }}
shell: bash

View file

@ -1,6 +1,6 @@
# 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-126.0.6478.26-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-126.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-127.0.6533.26-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-127.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
@ -8,9 +8,9 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->126.0.6478.26<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->127.0.6533.26<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->126.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->127.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.

View file

@ -1,3 +1,3 @@
REMOTE_URL="https://github.com/mozilla/gecko-dev"
BASE_BRANCH="release"
BASE_REVISION="f8704c84a751716bad093b9bdc482db53fe5b3ea"
BASE_REVISION="bd7e0ac24a6fb1cddde3e45ea191b7abcc90cf56"

View file

@ -146,6 +146,7 @@ class NetworkRequest {
this._expectingInterception = false;
this._expectingResumedRequest = undefined; // { method, headers, postData }
this._sentOnResponse = false;
this._fulfilled = false;
if (this._pageNetwork)
appendExtraHTTPHeaders(httpChannel, this._pageNetwork.combinedExtraHTTPHeaders());
@ -194,6 +195,7 @@ class NetworkRequest {
// Public interception API.
fulfill(status, statusText, headers, base64body) {
this._fulfilled = true;
this._interceptedChannel.synthesizeStatus(status, statusText);
for (const header of headers) {
this._interceptedChannel.synthesizeHeader(header.name, header.value);
@ -801,7 +803,8 @@ class ResponseStorage {
return;
}
let encodings = [];
if ((request.httpChannel instanceof Ci.nsIEncodedChannel) && request.httpChannel.contentEncodings && !request.httpChannel.applyConversion) {
// Note: fulfilled request comes with decoded body right away.
if ((request.httpChannel instanceof Ci.nsIEncodedChannel) && request.httpChannel.contentEncodings && !request.httpChannel.applyConversion && !request._fulfilled) {
const encodingHeader = request.httpChannel.getResponseHeader("Content-Encoding");
encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
}

View file

@ -355,6 +355,7 @@ class PageTarget {
this._screencastRecordingInfo = undefined;
this._dialogs = new Map();
this.forcedColors = 'no-override';
this.disableCache = false;
this.mediumOverride = '';
this.crossProcessCookie = {
initScripts: [],
@ -461,12 +462,26 @@ class PageTarget {
this.updateReducedMotionOverride(browsingContext);
this.updateForcedColorsOverride(browsingContext);
this.updateForceOffline(browsingContext);
this.updateCacheDisabled(browsingContext);
}
updateForceOffline(browsingContext = undefined) {
(browsingContext || this._linkedBrowser.browsingContext).forceOffline = this._browserContext.forceOffline;
}
setCacheDisabled(disabled) {
this.disableCache = disabled;
this.updateCacheDisabled();
}
updateCacheDisabled(browsingContext = this._linkedBrowser.browsingContext) {
const enableFlags = Ci.nsIRequest.LOAD_NORMAL;
const disableFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
browsingContext.defaultLoadFlags = (this._browserContext.disableCache || this.disableCache) ? disableFlags : enableFlags;
}
updateTouchOverride(browsingContext = undefined) {
(browsingContext || this._linkedBrowser.browsingContext).touchEventsOverride = this._browserContext.touchOverride ? 'enabled' : 'none';
}
@ -837,6 +852,7 @@ class BrowserContext {
this.defaultPlatform = null;
this.touchOverride = false;
this.forceOffline = false;
this.disableCache = false;
this.colorScheme = 'none';
this.forcedColors = 'no-override';
this.reducedMotion = 'none';
@ -938,6 +954,12 @@ class BrowserContext {
page.updateForceOffline();
}
setCacheDisabled(disabled) {
this.disableCache = disabled;
for (const page of this.pages)
page.updateCacheDisabled();
}
async setDefaultViewport(viewport) {
this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined;

View file

@ -152,7 +152,6 @@ class PageAgent {
getFullAXTree: this._getFullAXTree.bind(this),
insertText: this._insertText.bind(this),
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
setCacheDisabled: this._setCacheDisabled.bind(this),
setFileInputFiles: this._setFileInputFiles.bind(this),
evaluate: this._runtime.evaluate.bind(this._runtime),
callFunction: this._runtime.callFunction.bind(this._runtime),
@ -162,15 +161,6 @@ class PageAgent {
];
}
_setCacheDisabled({cacheDisabled}) {
const enable = Ci.nsIRequest.LOAD_NORMAL;
const disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
const docShell = this._frameTree.mainFrame().docShell();
docShell.defaultLoadFlags = cacheDisabled ? disable : enable;
}
_emitAllEvents(frame) {
this._browserPage.emit('pageEventFired', {
frameId: frame.id(),
@ -519,71 +509,16 @@ class PageAgent {
false /* aIgnoreRootScrollFrame */,
true /* aFlushLayout */);
const {defaultPrevented: startPrevented} = await this._dispatchTouchEvent({
await this._dispatchTouchEvent({
type: 'touchstart',
modifiers,
touchPoints: [{x, y}]
});
const {defaultPrevented: endPrevented} = await this._dispatchTouchEvent({
await this._dispatchTouchEvent({
type: 'touchend',
modifiers,
touchPoints: [{x, y}]
});
if (startPrevented || endPrevented)
return;
const frame = this._frameTree.mainFrame();
const winUtils = frame.domWindow().windowUtils;
winUtils.jugglerSendMouseEvent(
'mousemove',
x,
y,
0 /*button*/,
0 /*clickCount*/,
modifiers,
false /*aIgnoreRootScrollFrame*/,
0.0 /*pressure*/,
5 /*inputSource*/,
true /*isDOMEventSynthesized*/,
false /*isWidgetEventSynthesized*/,
0 /*buttons*/,
winUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
true /*disablePointerEvent*/
);
winUtils.jugglerSendMouseEvent(
'mousedown',
x,
y,
0 /*button*/,
1 /*clickCount*/,
modifiers,
false /*aIgnoreRootScrollFrame*/,
0.0 /*pressure*/,
5 /*inputSource*/,
true /*isDOMEventSynthesized*/,
false /*isWidgetEventSynthesized*/,
1 /*buttons*/,
winUtils.DEFAULT_MOUSE_POINTER_ID /*pointerIdentifier*/,
true /*disablePointerEvent*/,
);
winUtils.jugglerSendMouseEvent(
'mouseup',
x,
y,
0 /*button*/,
1 /*clickCount*/,
modifiers,
false /*aIgnoreRootScrollFrame*/,
0.0 /*pressure*/,
5 /*inputSource*/,
true /*isDOMEventSynthesized*/,
false /*isWidgetEventSynthesized*/,
0 /*buttons*/,
winUtils.DEFAULT_MOUSE_POINTER_ID /*pointerIdentifier*/,
true /*disablePointerEvent*/,
);
}
async _dispatchDragEvent({type, x, y, modifiers}) {

View file

@ -186,6 +186,10 @@ class BrowserHandler {
this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled;
}
['Browser.setCacheDisabled']({browserContextId, cacheDisabled}) {
this._targetRegistry.browserContextForId(browserContextId).setCacheDisabled(cacheDisabled);
}
['Browser.setIgnoreHTTPSErrors']({browserContextId, ignoreHTTPSErrors}) {
this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors));
}

View file

@ -302,8 +302,8 @@ class PageHandler {
await this._pageTarget.activateAndRun(() => {});
}
async ['Page.setCacheDisabled'](options) {
return await this._contentPage.send('setCacheDisabled', options);
async ['Page.setCacheDisabled']({cacheDisabled}) {
return await this._pageTarget.setCacheDisabled(cacheDisabled);
}
async ['Page.addBinding']({ worldName, name, script }) {

View file

@ -322,6 +322,12 @@ const Browser = {
enabled: t.Boolean,
},
},
'setCacheDisabled': {
params: {
browserContextId: t.Optional(t.String),
cacheDisabled: t.Boolean,
},
},
'setGeolocationOverride': {
params: {
browserContextId: t.Optional(t.String),

View file

@ -57,7 +57,7 @@ index 8e9bf2b413585b5a3db9370eee5d57fb6c6716ed..5a3b194b54e3813c89989f13a214c989
* Return XPCOM wrapper for the internal accessible.
*/
diff --git a/browser/app/winlauncher/LauncherProcessWin.cpp b/browser/app/winlauncher/LauncherProcessWin.cpp
index b40e0fceb567c0d217adf284e13f434e49cc8467..2c4e6d5fbf8da40954ad6a5b15e412493e43b14e 100644
index 81d4ee91e9383693d794dbf68184a80b49b582c6..1a07e3511f73199fe0b248412d01d7b8a3744a66 100644
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -22,6 +22,7 @@
@ -68,7 +68,7 @@ index b40e0fceb567c0d217adf284e13f434e49cc8467..2c4e6d5fbf8da40954ad6a5b15e41249
#include <windows.h>
#include <processthreadsapi.h>
@@ -421,8 +422,18 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
@@ -425,8 +426,18 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
HANDLE stdHandles[] = {::GetStdHandle(STD_INPUT_HANDLE),
::GetStdHandle(STD_OUTPUT_HANDLE),
::GetStdHandle(STD_ERROR_HANDLE)};
@ -106,10 +106,10 @@ index f6d425f36a965f03ac82dbe3ab6cde06f12751ac..d60999ab2658b1e1e5f07a8aee530451
browser/chrome/browser/search-extensions/amazon/favicon.ico
browser/chrome/browser/search-extensions/amazondotcn/favicon.ico
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index 4068c0c165fcebd0a72f4d780bc0cbd680fe7a9c..fec7f8505d22485fa6ad4193d59387f493bd1ef8 100644
index 1b87a9ab4aec939acac1da54a2b6670cc581fe86..a638dbe8e2f260d8be550fa8eb5bf6f6c2c31080 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -197,6 +197,9 @@
@@ -185,6 +185,9 @@
@RESPATH@/chrome/remote.manifest
#endif
@ -120,7 +120,7 @@ index 4068c0c165fcebd0a72f4d780bc0cbd680fe7a9c..fec7f8505d22485fa6ad4193d59387f4
@RESPATH@/components/extensions-toolkit.manifest
@RESPATH@/browser/components/extensions-browser.manifest
diff --git a/devtools/server/socket/websocket-server.js b/devtools/server/socket/websocket-server.js
index 4236ec2921bd57c58cfffdf1cdcf509d76fca3db..23d0cb1f06bb8c7a1cac8fcec94a99fba5bfe3f2 100644
index d49c6fbf1bf83b832795fa674f6b41f223eef812..7ea3540947ff5f61b15f27fbf4b955649f8e9ff9 100644
--- a/devtools/server/socket/websocket-server.js
+++ b/devtools/server/socket/websocket-server.js
@@ -134,13 +134,12 @@ function writeHttpResponse(output, response) {
@ -167,31 +167,28 @@ index 4236ec2921bd57c58cfffdf1cdcf509d76fca3db..23d0cb1f06bb8c7a1cac8fcec94a99fb
const transportProvider = {
setListener(upgradeListener) {
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
index 0ef5e02d2ae365b4e7b30fd49771e547c030bcfc..0787518696fc90bba3dce8c1e1c00387c3e7a6c9 100644
index 6e1a1b689398fa6c3c73f2f243ae02c67a4476c8..9dcf71ce753cf11f295d83eb7653e940065c8e2c 100644
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -114,6 +114,20 @@ struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
mozilla::dom::PrefersColorSchemeOverride::None,
mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {};
@@ -106,8 +106,15 @@ struct ParamTraits<mozilla::dom::DisplayMode>
template <>
struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
- : public mozilla::dom::WebIDLEnumSerializer<
- mozilla::dom::PrefersColorSchemeOverride> {};
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::PrefersColorSchemeOverride> {};
+
+template <>
+struct ParamTraits<mozilla::dom::PrefersReducedMotionOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::PrefersReducedMotionOverride,
+ mozilla::dom::PrefersReducedMotionOverride::None,
+ mozilla::dom::PrefersReducedMotionOverride::EndGuard_> {};
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::PrefersReducedMotionOverride> {};
+
+template <>
+struct ParamTraits<mozilla::dom::ForcedColorsOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::ForcedColorsOverride,
+ mozilla::dom::ForcedColorsOverride::None,
+ mozilla::dom::ForcedColorsOverride::EndGuard_> {};
+
+ : public mozilla::dom::WebIDLEnumSerializer<mozilla::dom::ForcedColorsOverride> {};
template <>
struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
: public ContiguousEnumSerializer<
@@ -2793,6 +2807,40 @@ void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
@@ -2804,6 +2811,40 @@ void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
PresContextAffectingFieldChanged();
}
@ -233,10 +230,10 @@ index 0ef5e02d2ae365b4e7b30fd49771e547c030bcfc..0787518696fc90bba3dce8c1e1c00387
nsString&& aOldValue) {
MOZ_ASSERT(IsTop());
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844799eb4f3 100644
index 5ec95a61e4d3af265cbe7dd9d83f6535da1d103e..f8acafe6d58c429af27a38363e06ad546dfbfddd 100644
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -200,10 +200,10 @@ struct EmbedderColorSchemes {
@@ -199,10 +199,10 @@ struct EmbedderColorSchemes {
FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \
/* ScreenOrientation-related APIs */ \
FIELD(CurrentOrientationAngle, float) \
@ -249,7 +246,7 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
FIELD(EmbedderElementType, Maybe<nsString>) \
FIELD(MessageManagerGroup, nsString) \
FIELD(MaxTouchPointsOverride, uint8_t) \
@@ -241,6 +241,10 @@ struct EmbedderColorSchemes {
@@ -240,6 +240,10 @@ struct EmbedderColorSchemes {
* <browser> embedder element. */ \
FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \
FIELD(DisplayMode, dom::DisplayMode) \
@ -260,7 +257,7 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
/* The number of entries added to the session history because of this \
* browsing context. */ \
FIELD(HistoryEntryCount, uint32_t) \
@@ -933,6 +937,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
@@ -926,6 +930,14 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
return GetPrefersColorSchemeOverride();
}
@ -275,7 +272,7 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
bool IsInBFCache() const;
bool AllowJavascript() const { return GetAllowJavascript(); }
@@ -1097,6 +1109,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
@@ -1090,6 +1102,23 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void WalkPresContexts(Callback&&);
void PresContextAffectingFieldChanged();
@ -300,10 +297,10 @@ index 7554128cf49ce929c973aeddf5f50ede01829c28..81ae7ec9523b944654c048af6a9f6844
bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
index d5f85b1e571a7840425164a3c7715e8c70ed5c8b..5ba7484b1e7f817b2bb21dd6b5b35c6ffe2c1ca5 100644
index 84f2d2960a3fa642754e0c909f92d96865e39984..e91fc85fc7a7966d2d536839fab823ae88d1b4bd 100644
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -1466,6 +1466,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
@@ -1477,6 +1477,12 @@ void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
return;
}
@ -317,7 +314,7 @@ index d5f85b1e571a7840425164a3c7715e8c70ed5c8b..5ba7484b1e7f817b2bb21dd6b5b35c6f
}
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f81b5618d7 100644
index 3404597343e0d21c42c5adc2f2849888869cf75a..558f03f30672b9f0fdb68ba8d23a0d878d33643d 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -15,6 +15,12 @@
@ -379,7 +376,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
mAllowAuth(mItemType == typeContent),
mAllowKeywordFixup(false),
mDisableMetaRefreshWhenInactive(false),
@@ -3115,6 +3132,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
@@ -3101,6 +3118,214 @@ nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
return NS_OK;
}
@ -594,7 +591,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
NS_IMETHODIMP
nsDocShell::GetIsNavigating(bool* aOut) {
*aOut = mIsNavigating;
@@ -4803,7 +5028,7 @@ nsDocShell::GetVisibility(bool* aVisibility) {
@@ -4789,7 +5014,7 @@ nsDocShell::GetVisibility(bool* aVisibility) {
}
void nsDocShell::ActivenessMaybeChanged() {
@ -603,7 +600,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
if (RefPtr<PresShell> presShell = GetPresShell()) {
presShell->ActivenessMaybeChanged();
}
@@ -6722,6 +6947,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
@@ -6711,6 +6936,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
return false; // no entry to save into
}
@ -614,7 +611,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
MOZ_ASSERT(!mozilla::SessionHistoryInParent(),
"mOSHE cannot be non-null with SHIP");
nsCOMPtr<nsIDocumentViewer> viewer = mOSHE->GetDocumentViewer();
@@ -8454,6 +8683,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
@@ -8443,6 +8672,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
true, // aForceNoOpener
getter_AddRefs(newBC));
MOZ_ASSERT(!newBC);
@ -627,7 +624,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
return rv;
}
@@ -9566,6 +9801,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
@@ -9569,6 +9804,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
nsCOMPtr<nsIRequest> req;
@ -644,7 +641,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
if (NS_SUCCEEDED(rv)) {
@@ -12714,6 +12959,9 @@ class OnLinkClickEvent : public Runnable {
@@ -12732,6 +12977,9 @@ class OnLinkClickEvent : public Runnable {
mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
mTriggeringPrincipal);
}
@ -654,7 +651,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
return NS_OK;
}
@@ -12798,6 +13046,8 @@ nsresult nsDocShell::OnLinkClick(
@@ -12816,6 +13064,8 @@ nsresult nsDocShell::OnLinkClick(
nsCOMPtr<nsIRunnable> ev =
new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
aIsTrusted, aTriggeringPrincipal);
@ -664,7 +661,7 @@ index 0b8212fbf3f81ef6264f17fc8e84f91cde39c0f7..99d43d662978c0418231b8ea55d670f8
}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f2ced36d9 100644
index 82ac6c9ab9dbc102a429ab0fe6cb24b8fcdf477f..f6328c25349cf39fcce973adcf908782e8721447 100644
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -15,6 +15,7 @@
@ -699,7 +696,7 @@ index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f
// Create a content viewer within this nsDocShell for the given
// `WindowGlobalChild` actor.
nsresult CreateDocumentViewerForActor(
@@ -1003,6 +1014,8 @@ class nsDocShell final : public nsDocLoader,
@@ -1004,6 +1015,8 @@ class nsDocShell final : public nsDocLoader,
bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; }
@ -708,7 +705,7 @@ index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f
// Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
// load is requested in a subframe of the current DocShell, the subframe
// loadType may need to reflect the loadType of the parent document, or in
@@ -1294,6 +1307,16 @@ class nsDocShell final : public nsDocLoader,
@@ -1295,6 +1308,16 @@ class nsDocShell final : public nsDocLoader,
bool mAllowDNSPrefetch : 1;
bool mAllowWindowControl : 1;
bool mCSSErrorReportingEnabled : 1;
@ -726,7 +723,7 @@ index 9f2d9a17dc0d54be4bd09f8e04da6e85f987fe34..8ff6e67fedef0bf73297c4cfed569b8f
bool mAllowKeywordFixup : 1;
bool mDisableMetaRefreshWhenInactive : 1;
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
index 9e79b6831a74c6de7c6a6c56d9a024d29c1c704b..db5cd5586b74ec65d788480f9c5fca93eb562c21 100644
index 21f09a517e91644f81f5bb823f556c15cd06e51f..a68d30e0a60f03a0942ac1cd8a1f83804fdfd3e0 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -44,6 +44,7 @@ interface nsIURI;
@ -737,7 +734,7 @@ index 9e79b6831a74c6de7c6a6c56d9a024d29c1c704b..db5cd5586b74ec65d788480f9c5fca93
interface nsIEditor;
interface nsIEditingSession;
interface nsIInputStream;
@@ -767,6 +768,36 @@ interface nsIDocShell : nsIDocShellTreeItem
@@ -754,6 +755,36 @@ interface nsIDocShell : nsIDocShellTreeItem
*/
void synchronizeLayoutHistoryState();
@ -775,10 +772,10 @@ index 9e79b6831a74c6de7c6a6c56d9a024d29c1c704b..db5cd5586b74ec65d788480f9c5fca93
* This attempts to save any applicable layout history state (like
* scroll position) in the nsISHEntry. This is normally done
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe005669767a1b196 100644
index 4e9286a91e3b0f1114aa0a7aa6ff81dde615a73d..941a0c3dac71008ca760024a1e828f75f6af5182 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3705,6 +3705,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
@@ -3676,6 +3676,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
}
void Document::ApplySettingsFromCSP(bool aSpeculative) {
@ -788,7 +785,7 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
nsresult rv = NS_OK;
if (!aSpeculative) {
// 1) apply settings from regular CSP
@@ -3762,6 +3765,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
@@ -3733,6 +3736,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
MOZ_ASSERT(!mScriptGlobalObject,
"CSP must be initialized before mScriptGlobalObject is set!");
@ -800,7 +797,7 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
// If this is a data document - no need to set CSP.
if (mLoadedAsData) {
return NS_OK;
@@ -4557,6 +4565,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
@@ -4500,6 +4508,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
return false;
}
@ -811,7 +808,7 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
if (!fm->IsInActiveWindow(bc)) {
return false;
}
@@ -18878,6 +18890,68 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
@@ -18849,6 +18861,66 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
return PreferenceSheet::PrefsFor(*this).mColorScheme;
}
@ -837,7 +834,6 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
+ case dom::PrefersReducedMotionOverride::No_preference:
+ return false;
+ case dom::PrefersReducedMotionOverride::None:
+ case dom::PrefersReducedMotionOverride::EndGuard_:
+ break;
+ }
+ }
@ -866,7 +862,6 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
+ case dom::ForcedColorsOverride::None:
+ return false;
+ case dom::ForcedColorsOverride::No_override:
+ case dom::ForcedColorsOverride::EndGuard_:
+ break;
+ }
+ }
@ -881,10 +876,10 @@ index 0a2f5be08d0dcad40cd11f4b064a1287b8141e2f..a845203c6aaea8384e8835cbe0056697
if (!sLoadingForegroundTopLevelContentDocument) {
return false;
diff --git a/dom/base/Document.h b/dom/base/Document.h
index 3f8032594868b8aa1c8bec2d25b789518170eb0e..5017b426369378b892a224a88410f18e743da45f 100644
index a52c61addffc4a2344fa06cb0bceebe6a7ca9075..b0f8d65e5341bf277e48bef3b88cb4cc384fbc49 100644
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -4051,6 +4051,9 @@ class Document : public nsINode,
@@ -4044,6 +4044,9 @@ class Document : public nsINode,
// color-scheme meta tag.
ColorScheme DefaultColorScheme() const;
@ -895,10 +890,10 @@ index 3f8032594868b8aa1c8bec2d25b789518170eb0e..5017b426369378b892a224a88410f18e
static bool AutomaticStorageAccessPermissionCanBeGranted(
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index d4b04f809228fecc3e2dbf33dac42151ffc24b11..39dfddff7404e878cd9fcc8f3b9bb734ab72d743 100644
index 14a00b8ed85f69312a89990acbb5e0f9755bd832..4b07c28615920a95a2ba59247f8575515cac4235 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -335,14 +335,18 @@ void Navigator::GetAppName(nsAString& aAppName) const {
@@ -338,14 +338,18 @@ void Navigator::GetAppName(nsAString& aAppName) const {
* for more detail.
*/
/* static */
@ -919,7 +914,7 @@ index d4b04f809228fecc3e2dbf33dac42151ffc24b11..39dfddff7404e878cd9fcc8f3b9bb734
// Split values on commas.
for (nsDependentSubstring lang :
@@ -394,7 +398,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) {
@@ -397,7 +401,13 @@ void Navigator::GetLanguage(nsAString& aLanguage) {
}
void Navigator::GetLanguages(nsTArray<nsString>& aLanguages) {
@ -935,10 +930,10 @@ index d4b04f809228fecc3e2dbf33dac42151ffc24b11..39dfddff7404e878cd9fcc8f3b9bb734
// The returned value is cached by the binding code. The window listens to the
// accept languages change and will clear the cache when needed. It has to
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
index 998328cfc6f36fd579e4fe26629a92a1ceefa9fa..c73e48d8dfe5a66fb9eb7f6bf49527f88cabc884 100644
index e559dc4d6aef61b7012a27f3d6c3186a12a15319..9798a50789ce972c4d9e94419e20a5cde4cd552a 100644
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -216,7 +216,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
@@ -215,7 +215,7 @@ class Navigator final : public nsISupports, public nsWrapperCache {
StorageManager* Storage();
@ -948,10 +943,10 @@ index 998328cfc6f36fd579e4fe26629a92a1ceefa9fa..c73e48d8dfe5a66fb9eb7f6bf49527f8
dom::MediaCapabilities* MediaCapabilities();
dom::MediaSession* MediaSession();
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa916988872a18 100644
index c6f1687f73df6b1849a191ff8722dc9fc26fc3eb..9fdface27d5c9bd0c61b8af229a31be2356c46b5 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8697,7 +8697,8 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8711,7 +8711,8 @@ nsresult nsContentUtils::SendMouseEvent(
bool aIgnoreRootScrollFrame, float aPressure,
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized,
@ -961,7 +956,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
nsPoint offset;
nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
if (!widget) return NS_ERROR_FAILURE;
@@ -8705,6 +8706,7 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8719,6 +8720,7 @@ nsresult nsContentUtils::SendMouseEvent(
EventMessage msg;
Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
bool contextMenuKey = false;
@ -969,7 +964,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
if (aType.EqualsLiteral("mousedown")) {
msg = eMouseDown;
} else if (aType.EqualsLiteral("mouseup")) {
@@ -8729,6 +8731,12 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8743,6 +8745,12 @@ nsresult nsContentUtils::SendMouseEvent(
msg = eMouseHitTest;
} else if (aType.EqualsLiteral("MozMouseExploreByTouch")) {
msg = eMouseExploreByTouch;
@ -982,7 +977,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
} else {
return NS_ERROR_FAILURE;
}
@@ -8737,12 +8745,21 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8751,12 +8759,21 @@ nsresult nsContentUtils::SendMouseEvent(
aInputSourceArg = MouseEvent_Binding::MOZ_SOURCE_MOUSE;
}
@ -1006,7 +1001,7 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
event.pointerId = aIdentifier;
event.mModifiers = GetWidgetModifiers(aModifiers);
event.mButton = aButton;
@@ -8753,8 +8770,10 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8767,8 +8784,10 @@ nsresult nsContentUtils::SendMouseEvent(
event.mPressure = aPressure;
event.mInputSource = aInputSourceArg;
event.mClickCount = aClickCount;
@ -1018,10 +1013,10 @@ index 6d3a9f8937a7b37bf82a18362d7ee525e4e267f4..c598acdcda9d47cdd193011e8faa9169
nsPresContext* presContext = aPresShell->GetPresContext();
if (!presContext) return NS_ERROR_FAILURE;
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
index db68b67c5a3e60ac214231ac82fd9f652a697858..529b49b2c6c5f693747d847bca85e93415b9159c 100644
index 338fc097dede02a538f240ba4cc66307086c7f56..8345e47237bc40490bd17019a053cce4c3d74a91 100644
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2958,7 +2958,8 @@ class nsContentUtils {
@@ -3055,7 +3055,8 @@ class nsContentUtils {
int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure,
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
mozilla::PreventDefaultResult* aPreventDefault,
@ -1032,7 +1027,7 @@ index db68b67c5a3e60ac214231ac82fd9f652a697858..529b49b2c6c5f693747d847bca85e934
static void FirePageShowEventForFrameLoaderSwap(
nsIDocShellTreeItem* aItem,
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index 6c547a9fd8604ac7fd7b965be473bc4120b2fdf7..c9aa1951da7af70913f77c76bb7c68ba144adaeb 100644
index 9bc8340b9009717e0feecd5c14ff02be07a03daf..70ea04c7f11e6ccfadf72a82ec1741fac10ef5fd 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -685,6 +685,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
@ -1110,10 +1105,10 @@ index 63968c9b7a4e418e4c0de6e7a75fa215a36a9105..decf3ea3833ccdffd49a7aded2d600f9
MOZ_CAN_RUN_SCRIPT
nsresult SendTouchEventCommon(
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
index 60f45157cdfceb7ebf4f9a1681d0e59dc1822360..964d7cc1b847cd8ef21cef29be4fdc11168b18d8 100644
index 5a4cf78d65eee0adcbeca33787706113eca19de7..7c8016e422ccc9e86563eaa83c9acc1dad0e5967 100644
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1673,6 +1673,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
@@ -1675,6 +1675,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
(GetActiveBrowsingContext() == newRootBrowsingContext);
}
@ -1124,7 +1119,27 @@ index 60f45157cdfceb7ebf4f9a1681d0e59dc1822360..964d7cc1b847cd8ef21cef29be4fdc11
// Exit fullscreen if a website focuses another window
if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
!isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
@@ -2946,7 +2950,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
@@ -2242,6 +2246,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
bool aIsLeavingDocument, bool aAdjustWidget,
bool aRemainActive, Element* aElementToFocus,
uint64_t aActionId) {
+
LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId));
// hold a reference to the focused content, which may be null
@@ -2288,6 +2293,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
return true;
}
+ // Playwright: emulate focused page by never bluring when leaving document.
+ if (XRE_IsContentProcess() && aIsLeavingDocument && docShell && nsDocShell::Cast(docShell)->ShouldOverrideHasFocus()) {
+ return true;
+ }
+
// Keep a ref to presShell since dispatching the DOM event may cause
// the document to be destroyed.
RefPtr<PresShell> presShell = docShell->GetPresShell();
@@ -2947,7 +2957,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
}
}
@ -1136,10 +1151,10 @@ index 60f45157cdfceb7ebf4f9a1681d0e59dc1822360..964d7cc1b847cd8ef21cef29be4fdc11
// care of lowering the present active window. This happens in
// a separate runnable to avoid touching multiple windows in
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
index a10808b95d5f7c81942d2a513f63a72c7821061e..027b9a3c87811b794816bddb90ff67bc271f7faa 100644
index e28dcdb092b23558702377af32eece5d273d4cf3..a37ae9d6ccd978d5e84562450e7039d6a75d517b 100644
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -2510,10 +2510,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
@@ -2509,10 +2509,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
}();
if (!isContentAboutBlankInChromeDocshell) {
@ -1160,7 +1175,7 @@ index a10808b95d5f7c81942d2a513f63a72c7821061e..027b9a3c87811b794816bddb90ff67bc
}
}
@@ -2633,6 +2639,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
@@ -2632,6 +2638,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
}
}
@ -1193,10 +1208,10 @@ index 8337a7353fb8e97372f0b57bd0fd867506a9129f..e7dedd6d26125e481e1145337a0be6ab
// Outer windows only.
virtual void EnsureSizeAndPositionUpToDate() override;
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
index 11ca350f483458ba11f0ee170ce38e9785e8c70f..6bb310f6e96239388b4c92bd7bdf2d698fac8139 100644
index d5455e559639aee9328905b50f02c52e94db950b..3fbd1e0459e5391066fc6b3a4e30a841594b31bf 100644
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1362,6 +1362,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
@@ -1365,6 +1365,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
mozilla::GetBoxQuadsFromWindowOrigin(this, aOptions, aResult, aRv);
}
@ -1259,10 +1274,10 @@ index 11ca350f483458ba11f0ee170ce38e9785e8c70f..6bb310f6e96239388b4c92bd7bdf2d69
DOMQuad& aQuad, const GeometryNode& aFrom,
const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
index 0ba44cb08ffffde3bf9616e7e3b1fb33317181b6..b698e309e7bf658739b3a69a1d68f657a6c84e6e 100644
index 3a47992cc89176fe9500f7b1d5b74e4422cb9625..27e6da8498af5e4a3c37407a3a8ab592e28dc372 100644
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -2235,6 +2235,10 @@ class nsINode : public mozilla::dom::EventTarget {
@@ -2248,6 +2248,10 @@ class nsINode : public mozilla::dom::EventTarget {
nsTArray<RefPtr<DOMQuad>>& aResult,
ErrorResult& aRv);
@ -1302,7 +1317,7 @@ index cceb725d393d5e5f83c8f87491089c3fa1d57cc3..e906a7fb7c3fd72554613f640dcc272e
static bool DumpEnabled();
diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl
index 8600a844634eed78e0b8470eaf11144f8ebd230b..853a4c98b78092181ad15542ae48cd41e9c49a82 100644
index d70f3e18cc8e8f749e5057297161206129871453..2f2be2a6539203d1957bfe580a06ab70a512c053 100644
--- a/dom/chrome-webidl/BrowsingContext.webidl
+++ b/dom/chrome-webidl/BrowsingContext.webidl
@@ -53,6 +53,24 @@ enum PrefersColorSchemeOverride {
@ -1330,7 +1345,7 @@ index 8600a844634eed78e0b8470eaf11144f8ebd230b..853a4c98b78092181ad15542ae48cd41
/**
* Allowed overrides of platform/pref default behaviour for touch events.
*/
@@ -205,6 +223,12 @@ interface BrowsingContext {
@@ -209,6 +227,12 @@ interface BrowsingContext {
// Color-scheme simulation, for DevTools.
[SetterThrows] attribute PrefersColorSchemeOverride prefersColorSchemeOverride;
@ -1443,10 +1458,10 @@ index 7e1af00d05fbafa2d828e2c7e4dcc5c82d115f5b..e85af9718d064e4d2865bc944e9d4ba1
~Geolocation();
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index ae2c4af82c3827a521a79f8879a6f941ea68feca..92ea48eba6fb575ea1415d8713b49707b895561b 100644
index d5a65a17555b7764611803f7fb298712b2c60fd7..fdee7deaf0b14e01702b902f8b73256c281e76b8 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -58,6 +58,7 @@
@@ -57,6 +57,7 @@
#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLDataListElement.h"
#include "mozilla/dom/HTMLOptionElement.h"
@ -1454,11 +1469,12 @@ index ae2c4af82c3827a521a79f8879a6f941ea68feca..92ea48eba6fb575ea1415d8713b49707
#include "nsIFormControlFrame.h"
#include "nsITextControlFrame.h"
#include "nsIFrame.h"
@@ -784,6 +785,12 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
@@ -783,6 +784,13 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
return NS_ERROR_FAILURE;
}
+ nsDocShell* docShell = static_cast<nsDocShell*>(win->GetDocShell());
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ nsDocShell* docShell = win ? static_cast<nsDocShell*>(win->GetDocShell()) : nullptr;
+ if (docShell && docShell->IsFileInputInterceptionEnabled()) {
+ docShell->FilePickerShown(this);
+ return NS_OK;
@ -1468,7 +1484,7 @@ index ae2c4af82c3827a521a79f8879a6f941ea68feca..92ea48eba6fb575ea1415d8713b49707
return NS_OK;
}
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
index 564b24e3f89e0ce6e683902a7fc4e1c3a6f5faac..e4264b251e580bb13aa2941b26227541c74d3371 100644
index 6a0df1b435cee9cea3b7d7ca30d0aff0e31f8ac0..c17b24823dd873c025e407fcc635b7c678dfd8ed 100644
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -373,6 +373,26 @@ interface nsIDOMWindowUtils : nsISupports {
@ -1499,10 +1515,10 @@ index 564b24e3f89e0ce6e683902a7fc4e1c3a6f5faac..e4264b251e580bb13aa2941b26227541
* touchstart, touchend, touchmove, and touchcancel
*
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index 830ad8579a39d262a1fd2c86479f2ddc77c01ae2..b840b68c9b5545fc974e1dafacac2e74372f4e41 100644
index bdd10fdcb285e43778b55907ac05bfa973207e5e..d98808918ff8eccd6c51b4fbce1cce5e3745206a 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -1651,6 +1651,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
@@ -1652,6 +1652,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
if (postLayerization) {
postLayerization->Register();
}
@ -1777,7 +1793,7 @@ index 3b39538e51840cd9b1685b2efd2ff2e9ec83608a..c7bf4f2d53b58bbacb22b3ebebf6f3fc
return aGlobalOrNull;
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
index 50730b691b06409f47cb9172570866a7bb519abc..e5ae4f31b8f93f17943c24108a82c6370470e9f2 100644
index 11d09909f73fee425fd0f50b384c396a52e02a36..b0e668881bcd3b850de709ebf2557ae8391b8fe8 100644
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -22,6 +22,7 @@
@ -1824,10 +1840,10 @@ index 2f71b284ee5f7e11f117c447834b48355784448c..2640bd57123c2b03bf4b06a2419cd020
* returned quads are further translated relative to the window
* origin -- which is not the layout origin. Further translation
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6bc97778f 100644
index 02efb1205382850b41c38d5f6ee47092adcdc63e..28c8d05d0b5cc415f3d13a4588248f3844faf4f2 100644
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -985,7 +985,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
@@ -995,7 +995,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
AssertIsOnMainThread();
nsTArray<nsString> languages;
@ -1836,7 +1852,7 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
RuntimeService* runtime = RuntimeService::GetService();
if (runtime) {
@@ -1172,8 +1172,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
@@ -1182,8 +1182,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
}
// The navigator overridden properties should have already been read.
@ -1846,7 +1862,7 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
mNavigatorPropertiesLoaded = true;
}
@@ -1772,6 +1771,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
@@ -1789,6 +1788,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
}
}
@ -1860,7 +1876,7 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
template <typename Func>
void RuntimeService::BroadcastAllWorkers(const Func& aFunc) {
AssertIsOnMainThread();
@@ -2287,6 +2293,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
@@ -2304,6 +2310,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
}
}
@ -1876,10 +1892,10 @@ index bcfbe33925afb97e68305394fb38e42481682540..47bc46f34a93991f112292c851c539a6
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aCx);
diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h
index e6deb81f357043a937d032bb4b6c38207203f4d9..ff16582af9fbf550dfb7b5639658c34199524c45 100644
index f51076ac1480794989999d00577bc9cf1566d5f9..fe15b2e00dc8f0bf203f2af9aad86e16c996d43d 100644
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -108,6 +108,8 @@ class RuntimeService final : public nsIObserver {
@@ -109,6 +109,8 @@ class RuntimeService final : public nsIObserver {
void PropagateStorageAccessPermissionGranted(
const nsPIDOMWindowInner& aWindow);
@ -1902,17 +1918,17 @@ index d10dabb5c5ff8e17851edf2bd2efc08e74584d8e..53c4070c5fde43b27fb8fbfdcf4c23d8
bool IsWorkerGlobal(JSObject* global);
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
index acef06ba3abb006932f26f8a96fd73028f015150..940210c603d906ae0469d826b81ba8f537e7fef5 100644
index a8643981aa966e9324a5dbdb09b4fe57210dc581..5120df2607584a7cd50ea03aa997ef5ade5c8ee2 100644
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -679,6 +679,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
@@ -682,6 +682,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
}
};
+class ResetDefaultLocaleRunnable final : public WorkerControlRunnable {
+ public:
+ explicit ResetDefaultLocaleRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThread) {}
+ : WorkerControlRunnable(aWorkerPrivate, "ResetDefaultLocaleRunnable", WorkerThread) {}
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
@ -1924,7 +1940,7 @@ index acef06ba3abb006932f26f8a96fd73028f015150..940210c603d906ae0469d826b81ba8f5
class UpdateLanguagesRunnable final : public WorkerRunnable {
nsTArray<nsString> mLanguages;
@@ -1977,6 +1989,16 @@ void WorkerPrivate::UpdateContextOptions(
@@ -1993,6 +2005,16 @@ void WorkerPrivate::UpdateContextOptions(
}
}
@ -1941,7 +1957,7 @@ index acef06ba3abb006932f26f8a96fd73028f015150..940210c603d906ae0469d826b81ba8f5
void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
AssertIsOnParentThread();
@@ -5480,6 +5502,15 @@ void WorkerPrivate::UpdateContextOptionsInternal(
@@ -5489,6 +5511,15 @@ void WorkerPrivate::UpdateContextOptionsInternal(
}
}
@ -2032,10 +2048,10 @@ index 523e84c8c93f4221701f90f2e8ee146ec8e1adbd..98d5b1176e5378431b859a2dbd4d4e77
inline ClippedTime TimeClip(double time);
diff --git a/js/src/debugger/Object.cpp b/js/src/debugger/Object.cpp
index c5a4f1f6dcf95922b5c8506d6bd24ad4fd2f1efc..a79bb0ad42d1bbd0434cda27c118cdd099013ab2 100644
index 17528b0fd99ce8274e702746ff5674de4c3d4f2d..37fa52ae07381bec3504136b9bec0aa1ca110d6b 100644
--- a/js/src/debugger/Object.cpp
+++ b/js/src/debugger/Object.cpp
@@ -2450,7 +2450,11 @@ Maybe<Completion> DebuggerObject::call(JSContext* cx,
@@ -2468,7 +2468,11 @@ Maybe<Completion> DebuggerObject::call(JSContext* cx,
invokeArgs[i].set(args2[i]);
}
@ -2202,10 +2218,10 @@ index dac899f7558b26d6848da8b98ed8a93555c8751a..2a07d67fa1c2840b25085566e84dc3b2
// No boxes to return
return;
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index 4afd2b10dfdab527b63f17919c52a559b3ac3de6..787283c004d9baa7227f00c338e0322dca88f949 100644
index 31c21c337786b76306029149a925293a44c45c28..7aa4eb0b4980bb8ecdc4e10c91b1cd70e5d0ad08 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -10963,7 +10963,9 @@ bool PresShell::ComputeActiveness() const {
@@ -11127,7 +11127,9 @@ bool PresShell::ComputeActiveness() const {
if (!browserChild->IsVisible()) {
MOZ_LOG(gLog, LogLevel::Debug,
(" > BrowserChild %p is not visible", browserChild));
@ -2229,10 +2245,10 @@ index 7bb839ae18835d128dc9285b7f9dc5b5e06335af..09e3979d07447522ace740daf2b818a6
const mozilla::dom::Document*);
mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
diff --git a/layout/style/nsMediaFeatures.cpp b/layout/style/nsMediaFeatures.cpp
index c99eecb4867c623b8ccc6e6fb42986d71f795cc0..d127837d7e069818daaeb73ef42497226ff95e26 100644
index f888c127d4423336a8f51e2011fc42eaf6c33f11..51d6e069f4a81c42b4bf270ba44ae4f82b8df592 100644
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -257,11 +257,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) {
@@ -260,11 +260,11 @@ bool Gecko_MediaFeatures_MatchesPlatform(StylePlatform aPlatform) {
}
bool Gecko_MediaFeatures_PrefersReducedMotion(const Document* aDocument) {
@ -2250,7 +2266,7 @@ index c99eecb4867c623b8ccc6e6fb42986d71f795cc0..d127837d7e069818daaeb73ef4249722
bool Gecko_MediaFeatures_PrefersReducedTransparency(const Document* aDocument) {
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
index b15f24fffd7ea5a8a00319451fa7ebf9df32ec05..0279f6626f2ac938b0456d370fd956f2a23a036b 100644
index eb90324c37ce515e5a0fcf75412605ae57ead9ee..6733dd6c99d77399529945386caa3926960e4176 100644
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -653,7 +653,8 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
@ -2337,7 +2353,7 @@ index 155daa5cd52c4e93e1cc4559875868f4faf2c37e..02e8a2614a27a4cc9e1de760d4c48617
/**
* Set the status and reason for the forthcoming synthesized response.
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
index ca1f59e8847bd399fcf3018ede95d318265c374c..d114f0bcaddedc3553780ff7b97e395e28aff91e 100644
index d849eab7507f0665a8a79a1b11759afa3481f1ad..3a95d5201a1c1f2161a95e15beae86f2833a875f 100644
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -167,6 +167,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
@ -2387,10 +2403,10 @@ index c58fbc96391f8dcb585bd00b5ae8cba9088abd82..c121c891b61c9d60df770020c4ad0952
if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
index 3cb38dcf28a5cb3a8e70c336c8b1c822be59268f..4d9ae3ab666d4ba7b42730d50367720870f0183f 100644
index f2c47c42a6b6448ede3a6fef1510a3982336d5af..9e35df57102c93238de2e4d548bbe1205d227f3b 100644
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -1381,6 +1381,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
@@ -1382,6 +1382,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@ -2498,10 +2514,10 @@ index 73c83e526be1a3a252f995d0718e3975d50bffa7..db5977c54221e19e107a8325a0834302
(lazy.isRunningTests || Cu.isInAutomation) &&
this.SERVER_URL == "data:,#remote-settings-dummy/v1"
diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs
index c9ad30b28b069a122cf01c5e97b83dc68ef022ff..f32dbcfe26fe2f6b8a0d066d6dfd208f1afc510d 100644
index 8de45d95c2b942f069c898c93702bc7db2219369..be72e9678c3de31b1eaa72cfcb2c8be886f4a80f 100644
--- a/servo/components/style/gecko/media_features.rs
+++ b/servo/components/style/gecko/media_features.rs
@@ -293,10 +293,15 @@ pub enum ForcedColors {
@@ -292,10 +292,15 @@ pub enum ForcedColors {
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool {
@ -2597,10 +2613,10 @@ index 209af157a35cf9c4586424421ee19b3f680796b6..1feb61e2f18e9a13631addc935f00da0
/**
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
index 290344c58bf488b4aa955c2c58bdaa11048e2f48..2d419b90e6c5a910d3038380cd6c819eb8b0c768 100644
index 34bf1b9e19ae54f78d134b023af96820bc13a905..b85793edcd1996833420a026cb08706d648ece14 100644
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
@@ -3853,6 +3853,8 @@ UpdateService.prototype = {
@@ -3852,6 +3852,8 @@ UpdateService.prototype = {
},
get disabledForTesting() {
@ -2610,7 +2626,7 @@ index 290344c58bf488b4aa955c2c58bdaa11048e2f48..2d419b90e6c5a910d3038380cd6c819e
(Cu.isInAutomation ||
lazy.Marionette.running ||
diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild
index f554b692f7e874dd21be1ef783e899ba602ee7f3..dd6c94f8723ca227611c8390d9dcd4f292939703 100644
index b697eb1e3b02b0ffcc95a6e492dc23eb888488cc..0f4059341dbb3c2ecb2c46be0850e0d56e2a7453 100644
--- a/toolkit/toolkit.mozbuild
+++ b/toolkit/toolkit.mozbuild
@@ -155,6 +155,7 @@ if CONFIG["ENABLE_WEBDRIVER"]:
@ -2622,7 +2638,7 @@ index f554b692f7e874dd21be1ef783e899ba602ee7f3..dd6c94f8723ca227611c8390d9dcd4f2
]
diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp
index 7eb9e1104682d4eb47060654f43a1efa8b2a6bb2..a8315d6decf654b5302bea5beeea34140c300ded 100644
index 2a91deec5c10f87ed09f99b659baab77b2b638f2..78f4f30a0efe314563c6405f7b0848d2c3ca0551 100644
--- a/toolkit/xre/nsWindowsWMain.cpp
+++ b/toolkit/xre/nsWindowsWMain.cpp
@@ -14,8 +14,10 @@
@ -2636,10 +2652,10 @@ index 7eb9e1104682d4eb47060654f43a1efa8b2a6bb2..a8315d6decf654b5302bea5beeea3414
#include <windows.h>
#ifdef __MINGW32__
@@ -114,6 +116,19 @@ static void FreeAllocStrings(int argc, char** argv) {
int wmain(int argc, WCHAR** argv) {
@@ -137,6 +139,19 @@ int wmain(int argc, WCHAR** argv) {
SanitizeEnvironmentVariables();
SetDllDirectoryW(L"");
RemovePrefetchArguments(argc, argv);
+ bool hasJugglerPipe =
+ mozilla::CheckArg(argc, argv, "juggler-pipe", nullptr,
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND;
@ -2793,7 +2809,7 @@ index 4573e28470c5112f5ac2c5dd53e7a9d1ceedb943..b53b86d8e39f1de4b0d0f1a8d5d7295e
// OnStartRequest)
mDialog = nullptr;
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
index 205f73cfa127e15e171854165c92551fea957e84..6399525ea0abb55655c824b086a043d022392113 100644
index 1f77e095dbfa3acc046779114007d83fc1cfa087..2354abbab7af6f6bdc3bd628722f03ea401d236a 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -257,6 +257,8 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
@ -2886,7 +2902,7 @@ index 1c25e9d9a101233f71e92288a0f93125b81ac1c5..22cf67b0f6e3ddd2b3ed725a314ba6a9
}
#endif
diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h
index ea714704df53af78a55065ce7626798e0e674a23..a108e0459aeac9789adac7d7ec166abf94646454 100644
index d29b406524c8b4afe437b559e33b4b2b5824ee58..6bef9c1657f93f90f96735d76fedb6ba3888b5c1 100644
--- a/widget/MouseEvents.h
+++ b/widget/MouseEvents.h
@@ -258,6 +258,7 @@ class WidgetMouseEvent : public WidgetMouseEventBase,
@ -2935,7 +2951,7 @@ diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.
index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce8050630a1aa 100644
--- a/widget/cocoa/NativeKeyBindings.mm
+++ b/widget/cocoa/NativeKeyBindings.mm
@@ -528,6 +528,13 @@ void NativeKeyBindings::GetEditCommandsForTests(
@@ -528,6 +528,13 @@
break;
case KEY_NAME_INDEX_ArrowLeft:
if (aEvent.IsAlt()) {
@ -2949,7 +2965,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
break;
}
if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) {
@@ -550,6 +557,13 @@ void NativeKeyBindings::GetEditCommandsForTests(
@@ -550,6 +557,13 @@
break;
case KEY_NAME_INDEX_ArrowRight:
if (aEvent.IsAlt()) {
@ -2963,7 +2979,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
break;
}
if (aEvent.IsMeta() || (aEvent.IsControl() && aEvent.IsShift())) {
@@ -572,6 +586,10 @@ void NativeKeyBindings::GetEditCommandsForTests(
@@ -572,6 +586,10 @@
break;
case KEY_NAME_INDEX_ArrowUp:
if (aEvent.IsControl()) {
@ -2974,7 +2990,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
break;
}
if (aEvent.IsMeta()) {
@@ -582,7 +600,7 @@ void NativeKeyBindings::GetEditCommandsForTests(
@@ -582,7 +600,7 @@
!aEvent.IsShift()
? ToObjcSelectorPtr(@selector(moveToBeginningOfDocument:))
: ToObjcSelectorPtr(
@ -2983,7 +2999,7 @@ index e4bdf715e2fb899e97a5bfeb2e147127460d6047..3554f919480278b7353617481c7ce805
aCommands);
break;
}
@@ -609,6 +627,10 @@ void NativeKeyBindings::GetEditCommandsForTests(
@@ -609,6 +627,10 @@
break;
case KEY_NAME_INDEX_ArrowDown:
if (aEvent.IsControl()) {

View file

@ -1,3 +1,3 @@
REMOTE_URL="https://github.com/WebKit/WebKit.git"
BASE_BRANCH="main"
BASE_REVISION="e225c278f4c06f451ea92cc68b12986dd2a99979"
BASE_REVISION="b2ca06dc3d84b356d01cdf09a82049f80515fbfe"

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,7 @@ A few examples where it may come in handy:
All of that could be achieved via [APIRequestContext] methods.
The following examples rely on the [`Microsoft.Playwright.NUnit`](./test-runners.md) package which creates a Playwright and Page instance for each test.
The following examples rely on the [`Microsoft.Playwright.MSTest`](./test-runners.md) package which creates a Playwright and Page instance for each test.
<!-- TOC -->
@ -34,22 +34,19 @@ The following example demonstrates how to use Playwright to test issues creation
GitHub API requires authorization, so we'll configure the token once for all tests. While at it, we'll also set the `baseURL` to simplify the tests.
```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Playwright.NUnit;
using Microsoft.Playwright;
using NUnit.Framework;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestClass]
public class TestGitHubAPI : PlaywrightTest
{
static string API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
static string? API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
private IAPIRequestContext Request = null;
private IAPIRequestContext Request = null!;
[SetUp]
[TestInitialize]
public async Task SetUpAPITesting()
{
await CreateAPIRequestContext();
@ -71,7 +68,7 @@ public class TestGitHubAPI : PlaywrightTest
});
}
[TearDown]
[TestCleanup]
public async Task TearDownAPITesting()
{
await Request.DisposeAsync();
@ -83,36 +80,34 @@ public class TestGitHubAPI : PlaywrightTest
Now that we initialized request object we can add a few tests that will create new issues in the repository.
```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text.Json;
using Microsoft.Playwright.NUnit;
using Microsoft.Playwright;
using NUnit.Framework;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestFixture]
[TestClass]
public class TestGitHubAPI : PlaywrightTest
{
static string REPO = "test-repo-2";
static string REPO = "test";
static string USER = Environment.GetEnvironmentVariable("GITHUB_USER");
static string API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
static string? API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
private IAPIRequestContext Request = null;
private IAPIRequestContext Request = null!;
[Test]
[TestMethod]
public async Task ShouldCreateBugReport()
{
var data = new Dictionary<string, string>();
data.Add("title", "[Bug] report 1");
data.Add("body", "Bug description");
var data = new Dictionary<string, string>
{
{ "title", "[Bug] report 1" },
{ "body", "Bug description" }
};
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
Assert.True(newIssue.Ok);
await Expect(newIssue).ToBeOKAsync();
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
Assert.True(issues.Ok);
await Expect(newIssue).ToBeOKAsync();
var issuesJsonResponse = await issues.JsonAsync();
JsonElement? issue = null;
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
@ -125,23 +120,24 @@ public class TestGitHubAPI : PlaywrightTest
}
}
}
Assert.NotNull(issue);
Assert.IsNotNull(issue);
Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString());
}
[Test]
[TestMethod]
public async Task ShouldCreateFeatureRequests()
{
var data = new Dictionary<string, string>();
data.Add("title", "[Feature] request 1");
data.Add("body", "Feature description");
var data = new Dictionary<string, string>
{
{ "title", "[Feature] request 1" },
{ "body", "Feature description" }
};
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
Assert.True(newIssue.Ok);
await Expect(newIssue).ToBeOKAsync();
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
Assert.True(issues.Ok);
await Expect(newIssue).ToBeOKAsync();
var issuesJsonResponse = await issues.JsonAsync();
var issuesJson = (await issues.JsonAsync())?.EnumerateArray();
JsonElement? issue = null;
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
@ -154,7 +150,7 @@ public class TestGitHubAPI : PlaywrightTest
}
}
}
Assert.NotNull(issue);
Assert.IsNotNull(issue);
Assert.AreEqual("Feature description", issue?.GetProperty("body").GetString());
}
@ -167,11 +163,17 @@ public class TestGitHubAPI : PlaywrightTest
These tests assume that repository exists. You probably want to create a new one before running tests and delete it afterwards. Use `[SetUp]` and `[TearDown]` hooks for that.
```csharp
using System.Text.Json;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestClass]
public class TestGitHubAPI : PlaywrightTest
{
// ...
[SetUp]
[TestInitialize]
public async Task SetUpAPITesting()
{
await CreateAPIRequestContext();
@ -187,10 +189,10 @@ public class TestGitHubAPI : PlaywrightTest
["name"] = REPO,
},
});
Assert.True(resp.Ok);
await Expect(resp).ToBeOKAsync();
}
[TearDown]
[TestCleanup]
public async Task TearDownAPITesting()
{
await DeleteTestRepository();
@ -200,7 +202,7 @@ public class TestGitHubAPI : PlaywrightTest
private async Task DeleteTestRepository()
{
var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO);
Assert.True(resp.Ok);
await Expect(resp).ToBeOKAsync();
}
}
```
@ -210,36 +212,34 @@ public class TestGitHubAPI : PlaywrightTest
Here is the complete example of an API test:
```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text.Json;
using Microsoft.Playwright.NUnit;
using Microsoft.Playwright;
using NUnit.Framework;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestFixture]
[TestClass]
public class TestGitHubAPI : PlaywrightTest
{
static string REPO = "test-repo-2";
static string USER = Environment.GetEnvironmentVariable("GITHUB_USER");
static string API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
static string? API_TOKEN = Environment.GetEnvironmentVariable("GITHUB_API_TOKEN");
private IAPIRequestContext Request = null;
private IAPIRequestContext Request = null!;
[Test]
[TestMethod]
public async Task ShouldCreateBugReport()
{
var data = new Dictionary<string, string>();
data.Add("title", "[Bug] report 1");
data.Add("body", "Bug description");
var data = new Dictionary<string, string>
{
{ "title", "[Bug] report 1" },
{ "body", "Bug description" }
};
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
Assert.True(newIssue.Ok);
await Expect(newIssue).ToBeOKAsync();
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
Assert.True(issues.Ok);
await Expect(newIssue).ToBeOKAsync();
var issuesJsonResponse = await issues.JsonAsync();
JsonElement? issue = null;
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
@ -252,23 +252,24 @@ public class TestGitHubAPI : PlaywrightTest
}
}
}
Assert.NotNull(issue);
Assert.IsNotNull(issue);
Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString());
}
[Test]
[TestMethod]
public async Task ShouldCreateFeatureRequests()
{
var data = new Dictionary<string, string>();
data.Add("title", "[Feature] request 1");
data.Add("body", "Feature description");
var data = new Dictionary<string, string>
{
{ "title", "[Feature] request 1" },
{ "body", "Feature description" }
};
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
Assert.True(newIssue.Ok);
await Expect(newIssue).ToBeOKAsync();
var issues = await Request.GetAsync("/repos/" + USER + "/" + REPO + "/issues");
Assert.True(issues.Ok);
await Expect(newIssue).ToBeOKAsync();
var issuesJsonResponse = await issues.JsonAsync();
var issuesJson = (await issues.JsonAsync())?.EnumerateArray();
JsonElement? issue = null;
foreach (JsonElement issueObj in issuesJsonResponse?.EnumerateArray())
@ -281,11 +282,11 @@ public class TestGitHubAPI : PlaywrightTest
}
}
}
Assert.NotNull(issue);
Assert.IsNotNull(issue);
Assert.AreEqual("Feature description", issue?.GetProperty("body").GetString());
}
[SetUp]
[TestInitialize]
public async Task SetUpAPITesting()
{
await CreateAPIRequestContext();
@ -294,14 +295,16 @@ public class TestGitHubAPI : PlaywrightTest
private async Task CreateAPIRequestContext()
{
var headers = new Dictionary<string, string>();
var headers = new Dictionary<string, string>
{
// We set this header per GitHub guidelines.
headers.Add("Accept", "application/vnd.github.v3+json");
{ "Accept", "application/vnd.github.v3+json" },
// Add authorization token to all requests.
// Assuming personal access token available in the environment.
headers.Add("Authorization", "token " + API_TOKEN);
{ "Authorization", "token " + API_TOKEN }
};
Request = await this.Playwright.APIRequest.NewContextAsync(new()
Request = await Playwright.APIRequest.NewContextAsync(new()
{
// All requests we send go to this API endpoint.
BaseURL = "https://api.github.com",
@ -318,10 +321,10 @@ public class TestGitHubAPI : PlaywrightTest
["name"] = REPO,
},
});
Assert.True(resp.Ok);
await Expect(resp).ToBeOKAsync();
}
[TearDown]
[TestCleanup]
public async Task TearDownAPITesting()
{
await DeleteTestRepository();
@ -331,7 +334,7 @@ public class TestGitHubAPI : PlaywrightTest
private async Task DeleteTestRepository()
{
var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO);
Assert.True(resp.Ok);
await Expect(resp).ToBeOKAsync();
}
}
```
@ -344,14 +347,16 @@ project to check that it appears at the top of the list. The check is performed
```csharp
class TestGitHubAPI : PageTest
{
[Test]
[TestMethod]
public async Task LastCreatedIssueShouldBeFirstInTheList()
{
var data = new Dictionary<string, string>();
data.Add("title", "[Feature] request 1");
data.Add("body", "Feature description");
var data = new Dictionary<string, string>
{
{ "title", "[Feature] request 1" },
{ "body", "Feature description" }
};
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
Assert.True(newIssue.Ok);
await Expect(newIssue).ToBeOKAsync();
// When inheriting from 'PlaywrightTest' it only gives you a Playwright instance. To get a Page instance, either start
// a browser, context, and page manually or inherit from 'PageTest' which will launch it for you.
@ -368,9 +373,10 @@ The following test creates a new issue via user interface in the browser and the
it was created:
```csharp
// Make sure to extend from PageTest if you want to use the Page class.
class GitHubTests : PageTest
{
[Test]
[TestMethod]
public async Task LastCreatedIssueShouldBeOnTheServer()
{
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");
@ -378,10 +384,10 @@ class GitHubTests : PageTest
await Page.Locator("[aria-label='Title']").FillAsync("Bug report 1");
await Page.Locator("[aria-label='Comment body']").FillAsync("Bug description");
await Page.Locator("text=Submit new issue").ClickAsync();
String issueId = Page.Url.Substring(Page.Url.LastIndexOf('/'));
var issueId = Page.Url.Substring(Page.Url.LastIndexOf('/'));
var newIssue = await Request.GetAsync("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId);
Assert.True(newIssue.Ok);
await Expect(newIssue).ToBeOKAsync();
StringAssert.Contains(await newIssue.TextAsync(), "Bug report 1");
}
}

View file

@ -344,6 +344,9 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
### option: APIRequestContext.fetch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26
### option: APIRequestContext.fetch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.get
* since: v1.16
- returns: <[APIResponse]>
@ -433,6 +436,9 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
### option: APIRequestContext.get.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26
### option: APIRequestContext.get.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.head
* since: v1.16
- returns: <[APIResponse]>
@ -486,6 +492,9 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.head.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26
### option: APIRequestContext.head.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.patch
* since: v1.16
- returns: <[APIResponse]>
@ -539,6 +548,9 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.patch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26
### option: APIRequestContext.patch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.post
* since: v1.16
- returns: <[APIResponse]>
@ -713,6 +725,9 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
### option: APIRequestContext.post.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26
### option: APIRequestContext.post.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.put
* since: v1.16
- returns: <[APIResponse]>
@ -766,6 +781,9 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.put.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26
### option: APIRequestContext.put.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.storageState
* since: v1.16
- returns: <[Object]>

View file

@ -102,7 +102,7 @@ context.BackgroundPage += (_, backgroundPage) =>
* since: v1.45
- type: <[Clock]>
Playwright is using [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) to fake timers and clock.
Playwright has ability to mock clock and passage of time.
## event: BrowserContext.close
* since: v1.8
@ -357,18 +357,14 @@ await context.AddCookiesAsync(new[] { cookie1, cookie2 });
- `cookies` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>
- `url` ?<[string]> either url or domain / path are required. Optional.
- `domain` ?<[string]> either url or domain / path are required Optional.
- `path` ?<[string]> either url or domain / path are required Optional.
- `url` ?<[string]> Either url or domain / path are required. Optional.
- `domain` ?<[string]> For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or domain / path are required. Optional.
- `path` ?<[string]> Either url or domain / path are required Optional.
- `expires` ?<[float]> Unix time in seconds. Optional.
- `httpOnly` ?<[boolean]> Optional.
- `secure` ?<[boolean]> Optional.
- `sameSite` ?<[SameSiteAttribute]<"Strict"|"Lax"|"None">> Optional.
Adds cookies to the browser context.
For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
## async method: BrowserContext.addInitScript
* since: v1.8
@ -748,83 +744,6 @@ await page.SetContentAsync("<script>\n" +
await page.GetByRole(AriaRole.Button).ClickAsync();
```
An example of passing an element handle:
```js
await context.exposeBinding('clicked', async (source, element) => {
console.log(await element.textContent());
}, { handle: true });
await page.setContent(`
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
`);
```
```java
context.exposeBinding("clicked", (source, args) -> {
ElementHandle element = (ElementHandle) args[0];
System.out.println(element.textContent());
return null;
}, new BrowserContext.ExposeBindingOptions().setHandle(true));
page.setContent("" +
"<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
```
```python async
async def print(source, element):
print(await element.text_content())
await context.expose_binding("clicked", print, handle=true)
await page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```python sync
def print(source, element):
print(element.text_content())
context.expose_binding("clicked", print, handle=true)
page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```csharp
var result = new TaskCompletionSource<string>();
var page = await Context.NewPageAsync();
await Context.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
{
return result.TrySetResult(await t.AsElement().TextContentAsync());
});
await page.SetContentAsync("<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
await page.ClickAsync("div");
// Note: it makes sense to await the result here, because otherwise, the context
// gets closed and the binding function will throw an exception.
Assert.AreEqual("Click me", await result.Task);
```
### param: BrowserContext.exposeBinding.name
* since: v1.8
- `name` <[string]>
@ -839,6 +758,7 @@ Callback function that will be called in the Playwright's context.
### option: BrowserContext.exposeBinding.handle
* since: v1.8
* deprecated: This option will be removed in the future.
- `handle` <[boolean]>
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@ -1044,21 +964,22 @@ specified.
- `permissions` <[Array]<[string]>>
A permission or an array of permissions to grant. Permissions can be one of the following values:
* `'geolocation'`
* `'midi'`
* `'midi-sysex'` (system-exclusive midi)
* `'notifications'`
* `'camera'`
* `'microphone'`
* `'background-sync'`
* `'ambient-light-sensor'`
* `'accelerometer'`
* `'gyroscope'`
* `'magnetometer'`
* `'accessibility-events'`
* `'ambient-light-sensor'`
* `'background-sync'`
* `'camera'`
* `'clipboard-read'`
* `'clipboard-write'`
* `'geolocation'`
* `'gyroscope'`
* `'magnetometer'`
* `'microphone'`
* `'midi-sysex'` (system-exclusive midi)
* `'midi'`
* `'notifications'`
* `'payment-handler'`
* `'storage-access'`
### option: BrowserContext.grantPermissions.origin
* since: v1.8

View file

@ -1,86 +1,242 @@
# class: Clock
* since: v1.45
Playwright uses [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) for clock emulation. Clock is installed for the entire [BrowserContext], so the time
Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Learn more about [clock emulation](../clock.md).
Note that clock is installed for the entire [BrowserContext], so the time
in all the pages and iframes is controlled by the same clock.
## async method: Clock.fastForward
* since: v1.45
Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and
reopening it later, after given time.
**Usage**
```js
await page.clock.fastForward(1000);
await page.clock.fastForward('30:00');
```
```python async
await page.clock.fast_forward(1000)
await page.clock.fast_forward("30:00")
```
```python sync
page.clock.fast_forward(1000)
page.clock.fast_forward("30:00")
```
```java
page.clock().fastForward(1000);
page.clock().fastForward("30:00");
```
```csharp
await page.Clock.FastForwardAsync(1000);
await page.Clock.FastForwardAsync("30:00");
```
### param: Clock.fastForward.ticks
* since: v1.45
- `ticks` <[long]|[string]>
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
## async method: Clock.install
* since: v1.45
Creates a clock and installs it globally.
Install fake implementations for the following time-related functions:
### option: Clock.install.now
* `Date`
* `setTimeout`
* `clearTimeout`
* `setInterval`
* `clearInterval`
* `requestAnimationFrame`
* `cancelAnimationFrame`
* `requestIdleCallback`
* `cancelIdleCallback`
* `performance`
Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [`method: Clock.runFor`] and [`method: Clock.fastForward`] for more information.
### option: Clock.install.time
* since: v1.45
- `now` <[int]|[Date]>
- `time` <[long]|[string]|[Date]>
Install fake timers with the specified unix epoch (default: 0).
Time to initialize with, current system time by default.
### option: Clock.install.toFake
* since: v1.45
- `toFake` <[Array]<[FakeMethod]<"setTimeout"|"clearTimeout"|"setInterval"|"clearInterval"|"Date"|"requestAnimationFrame"|"cancelAnimationFrame"|"requestIdleCallback"|"cancelIdleCallback"|"performance">>>
An array with names of global methods and APIs to fake. For instance, `await page.clock.install({ toFake: ['setTimeout'] })` will fake only `setTimeout()`.
By default, `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` and `Date` are faked.
### option: Clock.install.loopLimit
* since: v1.45
- `loopLimit` <[int]>
The maximum number of timers that will be run when calling [`method: Clock.runAll`]. Defaults to `1000`.
### option: Clock.install.shouldAdvanceTime
* since: v1.45
- `shouldAdvanceTime` <[boolean]>
Tells `@sinonjs/fake-timers` to increment mocked time automatically based on the real system time shift (e.g., the mocked time will be incremented by
20ms for every 20ms change in the real system time). Defaults to `false`.
### option: Clock.install.advanceTimeDelta
* since: v1.45
- `advanceTimeDelta` <[int]>
Relevant only when using with [`option: shouldAdvanceTime`]. Increment mocked time by advanceTimeDelta ms every advanceTimeDelta ms change
in the real system time (default: 20).
## async method: Clock.jump
## async method: Clock.runFor
* since: v1.45
Advance the clock by jumping forward in time, firing callbacks at most once. Returns fake milliseconds since the unix epoch.
This can be used to simulate the JS engine (such as a browser) being put to sleep and resumed later, skipping intermediary timers.
Advance the clock, firing all the time-related callbacks.
### param: Clock.jump.time
**Usage**
```js
await page.clock.runFor(1000);
await page.clock.runFor('30:00');
```
```python async
await page.clock.run_for(1000);
await page.clock.run_for("30:00")
```
```python sync
page.clock.run_for(1000);
page.clock.run_for("30:00")
```
```java
page.clock().runFor(1000);
page.clock().runFor("30:00");
```
```csharp
await page.Clock.RunForAsync(1000);
await page.Clock.RunForAsync("30:00");
```
### param: Clock.runFor.ticks
* since: v1.45
- `time` <[int]|[string]>
- `ticks` <[long]|[string]>
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
## async method: Clock.runAll
## async method: Clock.pauseAt
* since: v1.45
- returns: <[int]> Fake milliseconds since the unix epoch.
Runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well.
This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers.
It runs a maximum of [`option: loopLimit`] times after which it assumes there is an infinite loop of timers and throws an error.
Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers
are fired unless [`method: Clock.runFor`], [`method: Clock.fastForward`], [`method: Clock.pauseAt`] or [`method: Clock.resume`] is called.
Only fires due timers at most once.
This is equivalent to user closing the laptop lid for a while and reopening it at the specified time and
pausing.
## async method: Clock.runToLast
**Usage**
```js
await page.clock.pauseAt(new Date('2020-02-02'));
await page.clock.pauseAt('2020-02-02');
```
```python async
await page.clock.pause_at(datetime.datetime(2020, 2, 2))
await page.clock.pause_at("2020-02-02")
```
```python sync
page.clock.pause_at(datetime.datetime(2020, 2, 2))
page.clock.pause_at("2020-02-02")
```
```java
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
page.clock().pauseAt(format.parse("2020-02-02"));
page.clock().pauseAt("2020-02-02");
```
```csharp
await page.Clock.PauseAtAsync(DateTime.Parse("2020-02-02"));
await page.Clock.PauseAtAsync("2020-02-02");
```
### param: Clock.pauseAt.time
* since: v1.45
- returns: <[int]> Fake milliseconds since the unix epoch.
This takes note of the last scheduled timer when it is run, and advances the clock to that time firing callbacks as necessary.
If new timers are added while it is executing they will be run only if they would occur before this time.
This is useful when you want to run a test to completion, but the test recursively sets timers that would cause runAll to trigger an infinite loop warning.
- `time` <[long]|[string]|[Date]>
## async method: Clock.tick
## async method: Clock.resume
* since: v1.45
- returns: <[int]> Fake milliseconds since the unix epoch.
Advance the clock, firing callbacks if necessary. Returns fake milliseconds since the unix epoch.
Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
### param: Clock.tick.time
## async method: Clock.setFixedTime
* since: v1.45
- `time` <[int]|[string]>
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
Makes `Date.now` and `new Date()` return fixed fake time at all times,
keeps all the timers running.
**Usage**
```js
await page.clock.setFixedTime(Date.now());
await page.clock.setFixedTime(new Date('2020-02-02'));
await page.clock.setFixedTime('2020-02-02');
```
```python async
await page.clock.set_fixed_time(datetime.datetime.now())
await page.clock.set_fixed_time(datetime.datetime(2020, 2, 2))
await page.clock.set_fixed_time("2020-02-02")
```
```python sync
page.clock.set_fixed_time(datetime.datetime.now())
page.clock.set_fixed_time(datetime.datetime(2020, 2, 2))
page.clock.set_fixed_time("2020-02-02")
```
```java
page.clock().setFixedTime(new Date());
page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
page.clock().setFixedTime("2020-02-02");
```
```csharp
await page.Clock.SetFixedTimeAsync(DateTime.Now);
await page.Clock.SetFixedTimeAsync(new DateTime(2020, 2, 2));
await page.Clock.SetFixedTimeAsync("2020-02-02");
```
### param: Clock.setFixedTime.time
* since: v1.45
- `time` <[long]|[string]|[Date]>
Time to be set.
## async method: Clock.setSystemTime
* since: v1.45
Sets current system time but does not trigger any timers.
**Usage**
```js
await page.clock.setSystemTime(Date.now());
await page.clock.setSystemTime(new Date('2020-02-02'));
await page.clock.setSystemTime('2020-02-02');
```
```python async
await page.clock.set_system_time(datetime.datetime.now())
await page.clock.set_system_time(datetime.datetime(2020, 2, 2))
await page.clock.set_system_time("2020-02-02")
```
```python sync
page.clock.set_system_time(datetime.datetime.now())
page.clock.set_system_time(datetime.datetime(2020, 2, 2))
page.clock.set_system_time("2020-02-02")
```
```java
page.clock().setSystemTime(new Date());
page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
page.clock().setSystemTime("2020-02-02");
```
```csharp
await page.Clock.SetSystemTimeAsync(DateTime.Now);
await page.Clock.SetSystemTimeAsync(new DateTime(2020, 2, 2));
await page.Clock.SetSystemTimeAsync("2020-02-02");
```
### param: Clock.setSystemTime.time
* since: v1.45
- `time` <[long]|[string]|[Date]>

View file

@ -953,6 +953,7 @@ When all steps combined have not finished during the specified [`option: timeout
Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they
are resolved relative to the current working directory. For empty array, clears the selected files.
For inputs with a `[webkitdirectory]` attribute, only a single directory path is supported.
This method expects [ElementHandle] to point to an
[input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), targets the control instead.

View file

@ -2164,6 +2164,7 @@ When all steps combined have not finished during the specified [`option: timeout
* since: v1.14
Upload file or multiple files into `<input type=file>`.
For inputs with a `[webkitdirectory]` attribute, only a single directory path is supported.
**Usage**
@ -2177,6 +2178,9 @@ await page.getByLabel('Upload files').setInputFiles([
path.join(__dirname, 'file2.txt'),
]);
// Select a directory
await page.getByLabel('Upload directory').setInputFiles(path.join(__dirname, 'mydir'));
// Remove all the selected files
await page.getByLabel('Upload file').setInputFiles([]);
@ -2195,6 +2199,9 @@ page.getByLabel("Upload file").setInputFiles(Paths.get("myfile.pdf"));
// Select multiple files
page.getByLabel("Upload files").setInputFiles(new Path[] {Paths.get("file1.txt"), Paths.get("file2.txt")});
// Select a directory
page.getByLabel("Upload directory").setInputFiles(Paths.get("mydir"));
// Remove all the selected files
page.getByLabel("Upload file").setInputFiles(new Path[0]);
@ -2210,6 +2217,9 @@ await page.get_by_label("Upload file").set_input_files('myfile.pdf')
# Select multiple files
await page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Select a directory
await page.get_by_label("Upload directory").set_input_files('mydir')
# Remove all the selected files
await page.get_by_label("Upload file").set_input_files([])
@ -2228,6 +2238,9 @@ page.get_by_label("Upload file").set_input_files('myfile.pdf')
# Select multiple files
page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Select a directory
page.get_by_label("Upload directory").set_input_files('mydir')
# Remove all the selected files
page.get_by_label("Upload file").set_input_files([])
@ -2246,6 +2259,9 @@ await page.GetByLabel("Upload file").SetInputFilesAsync("myfile.pdf");
// Select multiple files
await page.GetByLabel("Upload files").SetInputFilesAsync(new[] { "file1.txt", "file12.txt" });
// Select a directory
await page.GetByLabel("Upload directory").SetInputFilesAsync("mydir");
// Remove all the selected files
await page.GetByLabel("Upload file").SetInputFilesAsync(new[] {});

View file

@ -47,21 +47,19 @@ def test_status_becomes_submitted(page: Page) -> None:
```
```csharp
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestFixture]
[TestClass]
public class ExampleTests : PageTest
{
[Test]
[TestMethod]
public async Task StatusBecomesSubmitted()
{
// ..
await Page.GetByRole(AriaRole.Button).ClickAsync();
// ...
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync();
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");
}
}

View file

@ -155,7 +155,7 @@ page.Load -= PageLoadHandler;
* since: v1.45
- type: <[Clock]>
Playwright is using [@sinonjs/fake-timers](https://github.com/sinonjs/fake-timers) to fake timers and clock.
Playwright has ability to mock clock and passage of time.
## event: Page.close
* since: v1.8
@ -1817,80 +1817,6 @@ class PageExamples
}
```
An example of passing an element handle:
```js
await page.exposeBinding('clicked', async (source, element) => {
console.log(await element.textContent());
}, { handle: true });
await page.setContent(`
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
`);
```
```java
page.exposeBinding("clicked", (source, args) -> {
ElementHandle element = (ElementHandle) args[0];
System.out.println(element.textContent());
return null;
}, new Page.ExposeBindingOptions().setHandle(true));
page.setContent("" +
"<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
```
```python async
async def print(source, element):
print(await element.text_content())
await page.expose_binding("clicked", print, handle=true)
await page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```python sync
def print(source, element):
print(element.text_content())
page.expose_binding("clicked", print, handle=true)
page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```csharp
var result = new TaskCompletionSource<string>();
await page.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
{
return result.TrySetResult(await t.AsElement().TextContentAsync());
});
await page.SetContentAsync("<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
await page.ClickAsync("div");
Console.WriteLine(await result.Task);
```
### param: Page.exposeBinding.name
* since: v1.8
- `name` <[string]>
@ -1905,6 +1831,7 @@ Callback function that will be called in the Playwright's context.
### option: Page.exposeBinding.handle
* since: v1.8
* deprecated: This option will be removed in the future.
- `handle` <[boolean]>
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@ -3927,6 +3854,7 @@ An object containing additional HTTP headers to be sent with every request. All
Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they
are resolved relative to the current working directory. For empty array, clears the selected files.
For inputs with a `[webkitdirectory]` attribute, only a single directory path is supported.
This method expects [`param: selector`] to point to an
[input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), targets the control instead.

View file

@ -50,21 +50,19 @@ def test_navigates_to_login_page(page: Page) -> None:
```csharp
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestFixture]
[TestClass]
public class ExampleTests : PageTest
{
[Test]
[TestMethod]
public async Task NavigatetoLoginPage()
{
// ..
await Page.GetByText("Sing in").ClickAsync();
await Expect(Page.Locator("div#foobar")).ToHaveURL(new Regex(".*/login"));
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync();
await Expect(Page).ToHaveURLAsync(new Regex(".*/login"));
}
}
```

View file

@ -50,19 +50,18 @@ public class TestExample {
```
```csharp
using System.Threading.Tasks;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestFixture]
[TestClass]
public class ExampleTests : PageTest
{
[Test]
[TestMethod]
public async Task StatusBecomesSubmitted()
{
await Page.Locator("#submit-button").ClickAsync();
await Page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).ClickAsync();
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");
}
}

View file

@ -126,6 +126,16 @@ Whether to ignore HTTPS errors when sending network requests.
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
Defaults to `20`. Pass `0` to not follow redirects.
## method: RequestOptions.setMaxRetries
* since: v1.46
- returns: <[RequestOptions]>
### param: RequestOptions.setMaxRetries.maxRetries
* since: v1.46
- `maxRetries` <[int]>
Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
## method: RequestOptions.setMethod
* since: v1.18
- returns: <[RequestOptions]>

View file

@ -20,3 +20,23 @@ Dispatches a `touchstart` and `touchend` event with a single touch at the positi
### param: Touchscreen.tap.y
* since: v1.8
- `y` <[float]>
## async method: Touchscreen.touch
* since: v1.46
Synthesizes a touch event.
### param: Touchscreen.touch.type
* since: v1.46
- `type` <[TouchType]<"touchstart"|"touchend"|"touchmove"|"touchcancel">>
Type of the touch event.
### param: Touchscreen.touch.touches
* since: v1.46
- `touchPoints` <[Array]<[Object]>>
- `x` <[float]> x coordinate of the event in CSS pixels.
- `y` <[float]> y coordinate of the event in CSS pixels.
- `id` ?<[int]> Identifier used to track the touch point between events, must be unique within an event. Optional.
List of touch points for this event. `id` is a unique identifier of a touch point that helps identify it between touch events for the duration of its movement around the surface.

View file

@ -458,6 +458,12 @@ Whether to ignore HTTPS errors when sending network requests. Defaults to `false
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
Defaults to `20`. Pass `0` to not follow redirects.
## js-python-csharp-fetch-option-maxretries
* langs: js, python, csharp
- `maxRetries` <[int]>
Maximum number of times socket errors should be retried. Currently only `ECONNRESET` error is retried. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
## evaluate-expression
- `expression` <[string]>

View file

@ -15,7 +15,7 @@ We recommend to create `playwright/.auth` directory and add it to your `.gitigno
```bash tab=bash-bash
mkdir -p playwright/.auth
echo "\nplaywright/.auth" >> .gitignore
echo $'\nplaywright/.auth' >> .gitignore
```
```batch tab=bash-batch

View file

@ -604,6 +604,24 @@ $Env:PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT="120000"
pwsh bin/Debug/netX/playwright.ps1 install
```
If you are [installing dependencies](#install-system-dependencies) and need to use a proxy on Linux, make sure to run the command as a root user. Otherwise, Playwright will attempt to become a root and will not pass environment variables like `HTTPS_PROXY` to the linux package manager.
```bash js
sudo HTTPS_PROXY=https://192.0.2.1 npx playwright install-deps
```
```bash java
sudo HTTPS_PROXY=https://192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps"
```
```bash python
sudo HTTPS_PROXY=https://192.0.2.1 playwright install-deps
```
```bash csharp
sudo HTTPS_PROXY=https://192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install-deps
```
## Download from artifact repository
By default, Playwright downloads browsers from Microsoft's CDN.

View file

@ -628,65 +628,15 @@ DEBUG=pw:browser dotnet test
## Running headed
By default, Playwright launches browsers in headless mode. This can be changed by passing a flag when the browser is launched.
```js
// Works across chromium, firefox and webkit
const { chromium } = require('playwright');
const browser = await chromium.launch({ headless: false });
```
```java
// Works across chromium, firefox and webkit
import com.microsoft.playwright.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
BrowserType chromium = playwright.chromium();
Browser browser = chromium.launch(new BrowserType.LaunchOptions().setHeadless(false));
}
}
}
```
```python async
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
# Works across chromium, firefox and webkit
browser = await p.chromium.launch(headless=False)
asyncio.run(main())
```
```python sync
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# Works across chromium, firefox and webkit
browser = p.chromium.launch(headless=False)
```
```csharp
using Microsoft.Playwright;
using var playwright = await Playwright.CreateAsync();
await playwright.Chromium.LaunchAsync(new()
{
Headless = false
});
```
By default, Playwright launches browsers in headless mode. See in our [Running tests](./running-tests.md#run-tests-in-headed-mode) guide how to run tests in headed mode.
On Linux agents, headed execution requires [Xvfb](https://en.wikipedia.org/wiki/Xvfb) to be installed. Our [Docker image](./docker.md) and GitHub Action have Xvfb pre-installed. To run browsers in headed mode with Xvfb, add `xvfb-run` before the actual command.
```bash js
xvfb-run node index.js
xvfb-run npx playwrght test
```
```bash python
xvfb-run python test.py
xvfb-run pytest
```
```bash java
xvfb-run mvn test

376
docs/src/clock.md Normal file
View file

@ -0,0 +1,376 @@
---
id: clock
title: "Clock"
---
import LiteYouTube from '@site/src/components/LiteYouTube';
## Introduction
Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Utilizing [Clock] functionality allows developers to manipulate and control time within tests, enabling the precise validation of features such as rendering time, timeouts, scheduled tasks without the delays and variability of real-time execution.
The [Clock] API provides the following methods to control time:
- `setFixedTime`: Sets the fixed time for `Date.now()` and `new Date()`.
- `install`: initializes the clock and allows you to:
- `pauseAt`: Pauses the time at a specific time.
- `fastForward`: Fast forwards the time.
- `runFor`: Runs the time for a specific duration.
- `resume`: Resumes the time.
- `setSystemTime`: Sets the current system time.
The recommended approach is to use `setFixedTime` to set the time to a specific value. If that doesn't work for your use case, you can use `install` which allows you to pause time later on, fast forward it, tick it, etc. `setSystemTime` is only recommended for advanced use cases.
:::note
[`property: Page.clock`] overrides native global classes and functions related to time allowing them to be manually controlled:
- `Date`
- `setTimeout`
- `clearTimeout`
- `setInterval`
- `clearInterval`
- `requestAnimationFrame`
- `cancelAnimationFrame`
- `requestIdleCallback`
- `cancelIdleCallback`
- `performance`
:::
## Test with predefined time
Often you only need to fake `Date.now` while keeping the timers going.
That way the time flows naturally, but `Date.now` always returns a fixed value.
```html
<div id="current-time" data-testid="current-time"></div>
<script>
const renderTime = () => {
document.getElementById('current-time').textContent =
new Date().toLocaleTimeString();
};
setInterval(renderTime, 1000);
</script>
```
```js
await page.clock.setFixedTime(new Date('2024-02-02T10:00:00'));
await page.goto('http://localhost:3333');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
await page.clock.setFixedTime(new Date('2024-02-02T10:30:00'));
// We know that the page has a timer that updates the time every second.
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
```
## Consistent time and timers
Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time.
In this case, you can install the clock and fast forward to the time of interest when testing.
```html
<div id="current-time" data-testid="current-time"></div>
<script>
const renderTime = () => {
document.getElementById('current-time').textContent =
new Date().toLocaleTimeString();
};
setInterval(renderTime, 1000);
</script>
```
```js
// Initialize clock with some time before the test time and let the page load
// naturally. `Date.now` will progress as the timers fire.
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');
// Pretend that the user closed the laptop lid and opened it again at 10am,
// Pause the time once reached that point.
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
// Assert the page state.
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
// Close the laptop lid again and open it at 10:30am.
await page.clock.fastForward('30:00');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
```
```python async
# Initialize clock with some time before the test time and let the page load
# naturally. `Date.now` will progress as the timers fire.
await page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
await page.goto("http://localhost:3333")
# Pretend that the user closed the laptop lid and opened it again at 10am.
# Pause the time once reached that point.
await page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
# Assert the page state.
await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
# Close the laptop lid again and open it at 10:30am.
await page.clock.fast_forward("30:00")
await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```
```python sync
# Initialize clock with some time before the test time and let the page load
# naturally. `Date.now` will progress as the timers fire.
page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
page.goto("http://localhost:3333")
# Pretend that the user closed the laptop lid and opened it again at 10am.
# Pause the time once reached that point.
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
# Assert the page state.
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
# Close the laptop lid again and open it at 10:30am.
page.clock.fast_forward("30:00")
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```
```java
// Initialize clock with some time before the test time and let the page load
// naturally. `Date.now` will progress as the timers fire.
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-02-02T08:00:00")));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
// Pretend that the user closed the laptop lid and opened it again at 10am.
// Pause the time once reached that point.
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
// Assert the page state.
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
// Close the laptop lid again and open it at 10:30am.
page.clock().fastForward("30:00");
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
```
```csharp
// Initialize clock with some time before the test time and let the page load naturally.
// `Date.now` will progress as the timers fire.
await Page.Clock.InstallAsync(new
{
Time = new DateTime(2024, 2, 2, 8, 0, 0)
});
await Page.GotoAsync("http://localhost:3333");
// Pretend that the user closed the laptop lid and opened it again at 10am.
// Pause the time once reached that point.
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
// Assert the page state.
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM");
// Close the laptop lid again and open it at 10:30am.
await Page.Clock.FastForwardAsync("30:00");
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM");
```
## Test inactivity monitoring
Inactivity monitoring is a common feature in web applications that logs out users after a period of inactivity.
Testing this feature can be tricky because you need to wait for a long time to see the effect.
With the help of the clock, you can speed up time and test this feature quickly.
```js
// Initial time does not matter for the test, so we can pick current time.
await page.clock.install();
await page.goto('http://localhost:3333');
// Interact with the page
await page.getByRole('button').click();
// Fast forward time 5 minutes as if the user did not do anything.
// Fast forward is like closing the laptop lid and opening it after 5 minutes.
// All the timers due will fire once immediately, as in the real browser.
await page.clock.fastForward('5:00');
// Check that the user was logged out automatically.
await expect(page.getByText('You have been logged out due to inactivity.')).toBeVisible();
```
```python async
# Initial time does not matter for the test, so we can pick current time.
await page.clock.install()
await page.goto("http://localhost:3333")
# Interact with the page
await page.get_by_role("button").click()
# Fast forward time 5 minutes as if the user did not do anything.
# Fast forward is like closing the laptop lid and opening it after 5 minutes.
# All the timers due will fire once immediately, as in the real browser.
await page.clock.fast_forward("5:00")
# Check that the user was logged out automatically.
await expect(page.getByText("You have been logged out due to inactivity.")).toBeVisible()
```
```python sync
# Initial time does not matter for the test, so we can pick current time.
page.clock.install()
page.goto("http://localhost:3333")
# Interact with the page
page.get_by_role("button").click()
# Fast forward time 5 minutes as if the user did not do anything.
# Fast forward is like closing the laptop lid and opening it after 5 minutes.
# All the timers due will fire once immediately, as in the real browser.
page.clock.fast_forward("5:00")
# Check that the user was logged out automatically.
expect(page.get_by_text("You have been logged out due to inactivity.")).to_be_visible()
```
```java
// Initial time does not matter for the test, so we can pick current time.
page.clock().install();
page.navigate("http://localhost:3333");
Locator locator = page.getByRole("button");
// Interact with the page
locator.click();
// Fast forward time 5 minutes as if the user did not do anything.
// Fast forward is like closing the laptop lid and opening it after 5 minutes.
// All the timers due will fire once immediately, as in the real browser.
page.clock().fastForward("5:00");
// Check that the user was logged out automatically.
assertThat(page.getByText("You have been logged out due to inactivity.")).isVisible();
```
```csharp
// Initial time does not matter for the test, so we can pick current time.
await Page.Clock.InstallAsync();
await page.GotoAsync("http://localhost:3333");
// Interact with the page
await page.GetByRole("button").ClickAsync();
// Fast forward time 5 minutes as if the user did not do anything.
// Fast forward is like closing the laptop lid and opening it after 5 minutes.
// All the timers due will fire once immediately, as in the real browser.
await Page.Clock.FastForwardAsync("5:00");
// Check that the user was logged out automatically.
await Expect(Page.GetByText("You have been logged out due to inactivity.")).ToBeVisibleAsync();
```
## Tick through time manually, firing all the timers consistently
In rare cases, you may want to tick through time manually, firing all timers and
animation frames in the process to achieve a fine-grained control over the passage of time.
```html
<div id="current-time" data-testid="current-time"></div>
<script>
const renderTime = () => {
document.getElementById('current-time').textContent =
new Date().toLocaleTimeString();
};
setInterval(renderTime, 1000);
</script>
```
```js
// Initialize clock with a specific time, let the page load naturally.
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');
// Pause the time flow, stop the timers, you now have manual control
// over the page time.
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
await page.clock.runFor(2000);
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:02 AM');
```
```python async
# Initialize clock with a specific time, let the page load naturally.
await page.clock.install(time=
datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst),
)
await page.goto("http://localhost:3333")
locator = page.get_by_test_id("current-time")
# Pause the time flow, stop the timers, you now have manual control
# over the page time.
await page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
await expect(locator).to_have_text("2/2/2024, 10:00:00 AM")
# Tick through time manually, firing all timers in the process.
# In this case, time will be updated in the screen 2 times.
await page.clock.run_for(2000)
await expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
```
```python sync
# Initialize clock with a specific time, let the page load naturally.
page.clock.install(
time=datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst),
)
page.goto("http://localhost:3333")
locator = page.get_by_test_id("current-time")
# Pause the time flow, stop the timers, you now have manual control
# over the page time.
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
expect(locator).to_have_text("2/2/2024, 10:00:00 AM")
# Tick through time manually, firing all timers in the process.
# In this case, time will be updated in the screen 2 times.
page.clock.run_for(2000)
expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
```
```java
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
// Initialize clock with a specific time, let the page load naturally.
page.clock().install(new Clock.InstallOptions()
.setTime(format.parse("2024-02-02T08:00:00")));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
// Pause the time flow, stop the timers, you now have manual control
// over the page time.
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
page.clock().runFor(2000);
assertThat(locator).hasText("2/2/2024, 10:00:02 AM");
```
```csharp
// Initialize clock with a specific time, let the page load naturally.
await Page.Clock.InstallAsync(new
{
Time = new DateTime(2024, 2, 2, 8, 0, 0, DateTimeKind.Pst)
});
await page.GotoAsync("http://localhost:3333");
var locator = page.GetByTestId("current-time");
// Pause the time flow, stop the timers, you now have manual control
// over the page time.
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
await Page.Clock.RunForAsync(2000);
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:02 AM");
```
## Related Videos
<LiteYouTube
id="54_aC-rVKHg"
title="Playwright 1.45"
/>

View file

@ -5,7 +5,7 @@ title: "Dialogs"
## Introduction
Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation.
Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation. For print dialogs, see [Print](#print-dialogs).
## alert(), confirm(), prompt() dialogs
@ -126,3 +126,55 @@ Page.Dialog += async (_, dialog) =>
};
await Page.CloseAsync(new() { RunBeforeUnload = true });
```
## Print dialogs
In order to assert that a print dialog via [`window.print`](https://developer.mozilla.org/en-US/docs/Web/API/Window/print) was triggered, you can use the following snippet:
```js
await page.goto('<url>');
await page.evaluate('(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()');
await page.getByText('Print it!').click();
await page.waitForFunction('window.waitForPrintDialog');
```
```java
page.navigate("<url>");
page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
page.getByText("Print it!").click();
page.waitForFunction("window.waitForPrintDialog");
```
```python async
await page.goto("<url>")
await page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()")
await page.get_by_text("Print it!").click()
await page.wait_for_function("window.waitForPrintDialog")
```
```python sync
page.goto("<url>")
page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()")
page.get_by_text("Print it!").click()
page.wait_for_function("window.waitForPrintDialog")
```
```csharp
await Page.GotoAsync("<url>");
await Page.EvaluateAsync("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
await Page.GetByText("Print it!").ClickAsync();
await Page.WaitForFunctionAsync("window.waitForPrintDialog");
```
This will wait for the print dialog to be opened after the button is clicked.
Make sure to evaluate the script before clicking the button / after the page is loaded.

View file

@ -5,7 +5,7 @@ title: "Docker"
## Introduction
[Dockerfile.jammy] can be used to run Playwright scripts in Docker environment. These image includes the [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies). The Playwright package/dependency is not included in the image and should be installed separately.
[Dockerfile.jammy] can be used to run Playwright scripts in Docker environment. This image includes the [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies). The Playwright package/dependency is not included in the image and should be installed separately.
## Usage
@ -111,6 +111,7 @@ We currently publish images with the following tags:
- `:next` - tip-of-tree image version based on Ubuntu 22.04 LTS (Jammy Jellyfish).
- `:next-jammy` - tip-of-tree image version based on Ubuntu 22.04 LTS (Jammy Jellyfish).
- `:v%%VERSION%%` - Playwright v%%VERSION%% release docker image based on Ubuntu 22.04 LTS (Jammy Jellyfish).
- `:v%%VERSION%%-noble` - Playwright v%%VERSION%% release docker image based on Ubuntu 24.04 LTS (Noble Numbat).
- `:v%%VERSION%%-jammy` - Playwright v%%VERSION%% release docker image based on Ubuntu 22.04 LTS (Jammy Jellyfish).
- `:v%%VERSION%%-focal` - Playwright v%%VERSION%% release docker image based on Ubuntu 20.04 LTS (Focal Fossa).
@ -121,6 +122,7 @@ It is recommended to always pin your Docker image to a specific version if possi
### Base images
We currently publish images based on the following [Ubuntu](https://hub.docker.com/_/ubuntu) versions:
- **Ubuntu 24.04 LTS** (Noble Numbat), image tags include `noble`
- **Ubuntu 22.04 LTS** (Jammy Jellyfish), image tags include `jammy`
- **Ubuntu 20.04 LTS** (Focal Fossa), image tags include `focal`

View file

@ -397,17 +397,17 @@ page.locator("#area").pressSequentially("Hello World!");
```python async
# Press keys one by one
await page.locator('#area').pressSequentially('Hello World!')
await page.locator('#area').press_sequentially('Hello World!')
```
```python sync
# Press keys one by one
page.locator('#area').pressSequentially('Hello World!')
page.locator('#area').press_sequentially('Hello World!')
```
```csharp
// Press keys one by one
await page.Locator("#area").TypeAsync("Hello World!");
await Page.Locator("#area").PressSequentiallyAsync("Hello World!");
```
This method will emit all the necessary keyboard events, with all the `keydown`, `keyup`, `keypress` events in place. You can even specify the optional `delay` between the key presses to simulate real user behavior.
@ -542,6 +542,9 @@ await page.getByLabel('Upload files').setInputFiles([
path.join(__dirname, 'file2.txt'),
]);
// Select a directory
await page.getByLabel('Upload directory').setInputFiles(path.join(__dirname, 'mydir'));
// Remove all the selected files
await page.getByLabel('Upload file').setInputFiles([]);
@ -560,6 +563,9 @@ page.getByLabel("Upload file").setInputFiles(Paths.get("myfile.pdf"));
// Select multiple files
page.getByLabel("Upload files").setInputFiles(new Path[] {Paths.get("file1.txt"), Paths.get("file2.txt")});
// Select a directory
page.getByLabel("Upload directory").setInputFiles(Paths.get("mydir"));
// Remove all the selected files
page.getByLabel("Upload file").setInputFiles(new Path[0]);
@ -575,6 +581,9 @@ await page.get_by_label("Upload file").set_input_files('myfile.pdf')
# Select multiple files
await page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Select a directory
await page.get_by_label("Upload directory").set_input_files('mydir')
# Remove all the selected files
await page.get_by_label("Upload file").set_input_files([])
@ -593,6 +602,9 @@ page.get_by_label("Upload file").set_input_files('myfile.pdf')
# Select multiple files
page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Select a directory
page.get_by_label("Upload directory").set_input_files('mydir')
# Remove all the selected files
page.get_by_label("Upload file").set_input_files([])
@ -611,6 +623,9 @@ await page.GetByLabel("Upload file").SetInputFilesAsync("myfile.pdf");
// Select multiple files
await page.GetByLabel("Upload files").SetInputFilesAsync(new[] { "file1.txt", "file12.txt" });
// Select a directory
await page.GetByLabel("Upload directory").SetInputFilesAsync("mydir");
// Remove all the selected files
await page.GetByLabel("Upload file").SetInputFilesAsync(new[] {});

View file

@ -7,16 +7,16 @@ title: "Installation"
Playwright was created specifically to accommodate the needs of end-to-end testing. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with native mobile emulation.
You can choose to use [NUnit base classes](./test-runners.md#nunit) or [MSTest base classes](./test-runners.md#mstest) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure.
You can choose to use [MSTest base classes](./test-runners.md#mstest) or [NUnit base classes](./test-runners.md#nunit) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure.
1. Start by creating a new project with `dotnet new`. This will create the `PlaywrightTests` directory which includes a `UnitTest1.cs` file:
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -41,10 +41,10 @@ cd PlaywrightTests
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -83,10 +83,10 @@ Edit the `UnitTest1.cs` file with the code below to create an example end-to-end
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -132,10 +132,8 @@ public class ExampleTest : PageTest
```csharp title="UnitTest1.cs"
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace PlaywrightTests;
@ -182,8 +180,8 @@ See our doc on [Running and Debugging Tests](./running-tests.md) to learn more a
- Playwright is distributed as a .NET Standard 2.0 library. We recommend .NET 8.
- Windows 10+, Windows Server 2016+ or Windows Subsystem for Linux (WSL).
- MacOS 12 Monterey, MacOS 13 Ventura, or MacOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04.
- macOS 13 Ventura, or macOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04, Ubuntu 24.04, on x86-64 and arm64 architecture.
## What's next
@ -192,4 +190,4 @@ See our doc on [Running and Debugging Tests](./running-tests.md) to learn more a
- [Generate tests with Codegen](./codegen-intro.md)
- [See a trace of your tests](./trace-viewer-intro.md)
- [Run tests on CI](./ci-intro.md)
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)

View file

@ -130,8 +130,8 @@ By default browsers launched with Playwright run headless, meaning no browser UI
- Java 8 or higher.
- Windows 10+, Windows Server 2016+ or Windows Subsystem for Linux (WSL).
- MacOS 12 Monterey, MacOS 13 Ventura, or MacOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04.
- macOS 13 Ventura, or macOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04, Ubuntu 24.04, on x86-64 and arm64 architecture.
## What's next

View file

@ -288,8 +288,8 @@ pnpm exec playwright --version
- Node.js 18+
- Windows 10+, Windows Server 2016+ or Windows Subsystem for Linux (WSL).
- MacOS 12 Monterey, MacOS 13 Ventura, or MacOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04, with x86-64 or arm64 architecture.
- macOS 13 Ventura, or macOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04, Ubuntu 24.04, on x86-64 and arm64 architecture.
## What's next

View file

@ -101,8 +101,8 @@ pip install pytest-playwright playwright -U
- Python 3.8 or higher.
- Windows 10+, Windows Server 2016+ or Windows Subsystem for Linux (WSL).
- MacOS 12 Monterey, MacOS 13 Ventura, or MacOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04.
- macOS 13 Ventura, or macOS 14 Sonoma.
- Debian 11, Debian 12, Ubuntu 20.04 or Ubuntu 22.04, Ubuntu 24.04, on x86-64 and arm64 architecture.
## What's next

View file

@ -30,7 +30,7 @@ You can choose any testing framework such as JUnit or TestNG based on your proje
## .NET
Playwright for .NET comes with [NUnit base classes](https://playwright.dev/dotnet/docs/test-runners#nunit) and [MSTest base classes](https://playwright.dev/dotnet/docs/test-runners#mstest) for writing end-to-end tests.
Playwright for .NET comes with [MSTest base classes](https://playwright.dev/dotnet/docs/test-runners#mstest) and [NUnit base classes](https://playwright.dev/dotnet/docs/test-runners#nunit) for writing end-to-end tests.
* [Documentation](https://playwright.dev/dotnet/docs/intro)
* [GitHub repo](https://github.com/microsoft/playwright-dotnet)

View file

@ -5,7 +5,7 @@ title: "Getting started - Library"
## Introduction
Playwright can either be used with the [NUnit](./test-runners.md#nunit) or [MSTest](./test-runners.md#mstest), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on.
Playwright can either be used with the [MSTest](./test-runners.md#mstest) or [NUnit](./test-runners.md#nunit), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on.
## Usage

View file

@ -1444,14 +1444,6 @@ page.getByText("orange").click();
await page.GetByText("orange").ClickAsync();
```
```html card
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
```
#### Filter by text
Use the [`method: Locator.filter`] to locate a specific item in a list.

View file

@ -4,6 +4,69 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.45
### Clock
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
* testing with predefined time;
* keeping consistent time and timers;
* monitoring inactivity;
* ticking through time manually.
```csharp
// Initialize clock with some time before the test time and let the page load naturally.
// `Date.now` will progress as the timers fire.
await Page.Clock.InstallAsync(new
{
Time = new DateTime(2024, 2, 2, 8, 0, 0)
});
await Page.GotoAsync("http://localhost:3333");
// Pretend that the user closed the laptop lid and opened it again at 10am.
// Pause the time once reached that point.
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
// Assert the page state.
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM");
// Close the laptop lid again and open it at 10:30am.
await Page.Clock.FastForwardAsync("30:00");
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM");
```
See [the clock guide](./clock.md) for more details.
### Miscellaneous
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
```csharp
await page.GetByLabel("Upload directory").SetInputFilesAsync("mydir");
```
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
```csharp
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
await page.Keyboard.PressAsync("ControlOrMeta+S");
```
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
### Browser Versions
* Chromium 127.0.6533.5
* Mozilla Firefox 127.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 126
* Microsoft Edge 126
## Version 1.44
### New APIs
@ -637,7 +700,7 @@ This version was also tested against the following stable channels:
### Other highlights
- New option `MaxRedirects` for [`method: APIRequestContext.get`] and others to limit redirect count.
- Codegen now supports NUnit and MSTest frameworks.
- Codegen now supports MSTest and NUnit frameworks.
- ASP .NET is now supported.
### Behavior Change

View file

@ -4,6 +4,67 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.45
### Clock
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
* testing with predefined time;
* keeping consistent time and timers;
* monitoring inactivity;
* ticking through time manually.
```java
// Initialize clock with some time before the test time and let the page load
// naturally. `Date.now` will progress as the timers fire.
page.clock().install(new Clock.InstallOptions().setTime("2024-02-02T08:00:00"));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
// Pretend that the user closed the laptop lid and opened it again at 10am.
// Pause the time once reached that point.
page.clock().pauseAt("2024-02-02T10:00:00");
// Assert the page state.
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
// Close the laptop lid again and open it at 10:30am.
page.clock().fastForward("30:00");
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
```
See [the clock guide](./clock.md) for more details.
### Miscellaneous
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
```java
page.getByLabel("Upload directory").setInputFiles(Paths.get("mydir"));
```
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
```java
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
page.keyboard.press("ControlOrMeta+S");
```
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
### Browser Versions
* Chromium 127.0.6533.5
* Mozilla Firefox 127.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 126
* Microsoft Edge 126
## Version 1.44
### New APIs

View file

@ -6,6 +6,103 @@ toc_max_heading_level: 2
import LiteYouTube from '@site/src/components/LiteYouTube';
## Version 1.45
<LiteYouTube
id="54_aC-rVKHg"
title="Playwright 1.45"
/>
### Clock
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
* testing with predefined time;
* keeping consistent time and timers;
* monitoring inactivity;
* ticking through time manually.
```js
// Initialize clock and let the page load naturally.
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');
// Pretend that the user closed the laptop lid and opened it again at 10am,
// Pause the time once reached that point.
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
// Assert the page state.
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');
// Close the laptop lid again and open it at 10:30am.
await page.clock.fastForward('30:00');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
```
See [the clock guide](./clock.md) for more details.
### Test runner
- New CLI option `--fail-on-flaky-tests` that sets exit code to `1` upon any flaky tests. Note that by default, the test runner exits with code `0` when all failed tests recovered upon a retry. With this option, the test run will fail in such case.
- New enviroment variable `PLAYWRIGHT_FORCE_TTY` controls whether built-in `list`, `line` and `dot` reporters assume a live terminal. For example, this could be useful to disable tty behavior when your CI environment does not handle ANSI control sequences well. Alternatively, you can enable tty behavior even when to live terminal is present, if you plan to post-process the output and handle control sequences.
```sh
# Avoid TTY features that output ANSI control sequences
PLAYWRIGHT_FORCE_TTY=0 npx playwright test
# Enable TTY features, assuming a terminal width 80
PLAYWRIGHT_FORCE_TTY=80 npx playwright test
```
- New options [`property: TestConfig.respectGitIgnore`] and [`property: TestProject.respectGitIgnore`] control whether files matching `.gitignore` patterns are excluded when searching for tests.
- New property `timeout` is now available for custom expect matchers. This property takes into account `playwright.config.ts` and `expect.configure()`.
```ts
import { expect as baseExpect } from '@playwright/test';
export const expect = baseExpect.extend({
async toHaveAmount(locator: Locator, expected: number, options?: { timeout?: number }) {
// When no timeout option is specified, use the config timeout.
const timeout = options?.timeout ?? this.timeout;
// ... implement the assertion ...
},
});
```
### Miscellaneous
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
```ts
await page.getByLabel('Upload directory').setInputFiles(path.join(__dirname, 'mydir'));
```
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
```ts
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
await page.keyboard.press('ControlOrMeta+S');
```
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
- New option `reason` in [`method: APIRequestContext.dispose`] that will be included in the error message of ongoing operations interrupted by the context disposal.
- New option `host` in [`method: BrowserType.launchServer`] allows to accept websocket connections on a specific address instead of unspecified `0.0.0.0`.
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
### Browser Versions
* Chromium 127.0.6533.5
* Mozilla Firefox 127.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 126
* Microsoft Edge 126
## Version 1.44
<LiteYouTube

View file

@ -4,6 +4,66 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.45
### Clock
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
* testing with predefined time;
* keeping consistent time and timers;
* monitoring inactivity;
* ticking through time manually.
```python
# Initialize clock with some time before the test time and let the page load
# naturally. `Date.now` will progress as the timers fire.
page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
page.goto("http://localhost:3333")
# Pretend that the user closed the laptop lid and opened it again at 10am.
# Pause the time once reached that point.
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
# Assert the page state.
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
# Close the laptop lid again and open it at 10:30am.
page.clock.fast_forward("30:00")
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```
See [the clock guide](./clock.md) for more details.
### Miscellaneous
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
```python
page.get_by_label("Upload directory").set_input_files('mydir')
```
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
```python
# Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
page.keyboard.press("ControlOrMeta+S")
```
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
### Browser Versions
* Chromium 127.0.6533.5
* Mozilla Firefox 127.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 126
* Microsoft Edge 126
## Version 1.44
### New APIs

View file

@ -22,7 +22,7 @@ Use the following command to run all tests.
dotnet test
```
### Run your tests in headed mode
### Run tests in headed mode
Use the following command to run your tests in headed mode opening a browser window for each test.
@ -109,10 +109,10 @@ dotnet test --filter "Name~GetStartedLink"
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -159,4 +159,4 @@ Check out our [debugging guide](./debug.md) to learn more about the [Playwright
- [Generate tests with Codegen](./codegen-intro.md)
- [See a trace of your tests](./trace-viewer-intro.md)
- [Run tests on CI](./ci-intro.md)
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)

View file

@ -85,6 +85,10 @@ See [here](./test-runners.md) for further details on how to run tests in paralle
See experimental [JUnit integration](./junit.md) to automatically initialize Playwright objects and more.
### Run tests in headed mode
If you prefer, you can run your tests in headed mode by using the `launch(new BrowserType.LaunchOptions().setHeadless(false))` option.
## What's Next
- [Debugging tests](./debug.md)

View file

@ -71,8 +71,7 @@ import { test, expect } from '@playwright/test';
test('basic test', {
annotation: {
type: 'issue',
description: 'feature tags API',
url: 'https://github.com/microsoft/playwright/issues/23180'
description: 'https://github.com/microsoft/playwright/issues/23180',
},
}, async ({ page }) => {
await page.goto('https://playwright.dev/');
@ -98,8 +97,7 @@ Test title.
- `tag` ?<[string]|[Array]<[string]>>
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]> Annotation type, for example `'issue'`.
- `description` ?<[string]> Optional annotation description.
- `url` ?<[string]> Optional for example an issue url.
- `description` ?<[string]> Optional annotation description, for example an issue url.
Additional test details.
@ -442,7 +440,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
Additional details for all tests in the group.
@ -571,7 +568,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -627,7 +623,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -681,7 +676,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -733,7 +727,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -789,7 +782,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -847,7 +839,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -900,7 +891,6 @@ Group title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.describe`] for details description.
@ -1119,7 +1109,6 @@ Test title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.(call)`] for test details description.
@ -1225,7 +1214,6 @@ Test title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.(call)`] for test details description.
@ -1303,7 +1291,6 @@ Test title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.(call)`] for test details description.
@ -1449,7 +1436,6 @@ Test title.
- `annotation` ?<[Object]|[Array]<[Object]>>
- `type` <[string]>
- `description` ?<[string]>
- `url` ?<[string]>
See [`method: Test.(call)`] for test details description.

View file

@ -482,27 +482,6 @@ export default defineConfig({
```
## property: TestConfig.shardingSeed
* since: v1.45
- type: ?<[string]>
Shuffle the order of test groups with a seed. By default tests are run in the order they are discovered, which is mostly alphabetical. This could lead to an uneven distribution of slow and fast tests. Shuffling the order of tests in a deterministic way can help to distribute the load more evenly.
The sharding seed is a string that is used to initialize a random number generator.
Learn more about [parallelism and sharding](../test-parallel.md) with Playwright Test.
**Usage**
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
export default defineConfig({
shardingSeed: 'string value'
});
```
## property: TestConfig.testDir
* since: v1.10
- type: ?<[string]>

View file

@ -365,7 +365,7 @@ Use [`property: TestConfig.timeout`] to change this option for all projects.
## property: TestProject.use
* since: v1.10
- type: <[Fixtures]>
- type: ?<[TestOptions]>
Options for all tests in this project, for example [`property: TestOptions.browserName`]. Learn more about [configuration](../test-configuration.md) and see [available options][TestOptions].

View file

@ -77,10 +77,10 @@ expect.set_options(timeout=10_000)
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">

View file

@ -232,12 +232,14 @@ Let's say you'd like to test following component:
```js title="input-media.tsx"
import React from 'react';
export const InputMedia: React.FC<{
type InputMediaProps = {
// Media is a complex browser object we can't send to Node while testing.
onChange: (media: Media) => void,
}> = ({ onChange }) => {
return <></> as any;
onChange(media: Media): void;
};
export function InputMedia(props: InputMediaProps) {
return <></> as any;
}
```
Create a story file for your component:
@ -246,12 +248,14 @@ Create a story file for your component:
import React from 'react';
import InputMedia from './import-media';
export const InputMediaForTest: React.FC<{
onMediaChange: (mediaName: string) => void,
}> = ({ onMediaChange }) => {
// Instead of sending a complex `media` object to the test, send the media name.
return <InputMedia onChange={media => onMediaChange(media.name)} />;
type InputMediaForTestProps = {
onMediaChange(mediaName: string): void;
};
export function InputMediaForTest(props: InputMediaForTestProps) {
// Instead of sending a complex `media` object to the test, send the media name.
return <InputMedia onChange={media => props.onMediaChange(media.name)} />;
}
// Export more stories here.
```
@ -265,7 +269,6 @@ test('changes the image', async ({ mount }) => {
<InputMediaForTest
onMediaChange={mediaName => {
mediaSelected = mediaName;
console.log({ mediaName });
}}
/>
);
@ -281,7 +284,179 @@ test('changes the image', async ({ mount }) => {
As a result, for every component you'll have a story file that exports all the stories that are actually tested.
These stories live in the browser and "convert" complex object into the simple objects that can be accessed in the test.
## Hooks
## Under the hood
Here is how component testing works:
- Once the tests are executed, Playwright creates a list of components that the tests need.
- It then compiles a bundle that includes these components and serves it using a local static web server.
- Upon the `mount` call within the test, Playwright navigates to the facade page `/playwright/index.html` of this bundle and tells it to render the component.
- Events are marshalled back to the Node.js environment to allow verification.
Playwright is using [Vite](https://vitejs.dev/) to create the components bundle and serve it.
## API reference
### props
Provide props to a component when mounted.
<Tabs
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
}>
<TabItem value="react">
```js
test('props', async ({ mount }) => {
const component = await mount(<Component msg="greetings" />);
});
```
</TabItem>
<TabItem value="solid">
```js
test('props', async ({ mount }) => {
const component = await mount(<Component msg="greetings" />);
});
```
</TabItem>
<TabItem value="svelte">
```js
test('props', async ({ mount }) => {
const component = await mount(Component, { props: { msg: 'greetings' } });
});
```
</TabItem>
<TabItem value="vue">
```js
test('props', async ({ mount }) => {
const component = await mount(Component, { props: { msg: 'greetings' } });
});
```
</TabItem>
</Tabs>
### callbacks / events
Provide callbacks/events to a component when mounted.
<Tabs
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
}>
<TabItem value="react">
```js
test('callback', async ({ mount }) => {
const component = await mount(<Component callback={() => {}} />);
});
```
</TabItem>
<TabItem value="solid">
```js
test('callback', async ({ mount }) => {
const component = await mount(<Component callback={() => {}} />);
});
```
</TabItem>
<TabItem value="svelte">
```js
test('event', async ({ mount }) => {
const component = await mount(Component, { on: { callback() {} } });
});
```
</TabItem>
<TabItem value="vue">
```js
test('event', async ({ mount }) => {
const component = await mount(Component, { on: { callback() {} } });
});
```
</TabItem>
</Tabs>
### children / slots
Provide children/slots to a component when mounted.
<Tabs
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
}>
<TabItem value="react">
```js
test('children', async ({ mount }) => {
const component = await mount(<Component>Child</Component>);
});
```
</TabItem>
<TabItem value="solid">
```js
test('children', async ({ mount }) => {
const component = await mount(<Component>Child</Component>);
});
```
</TabItem>
<TabItem value="svelte">
```js
test('slot', async ({ mount }) => {
const component = await mount(Component, { slots: { default: 'Slot' } });
});
```
</TabItem>
<TabItem value="vue">
```js
test('slot', async ({ mount }) => {
const component = await mount(Component, { slots: { default: 'Slot' } });
});
```
</TabItem>
</Tabs>
### hooks
You can use `beforeMount` and `afterMount` hooks to configure your app. This lets you setup things like your app router, fake server etc. giving you the flexibility you need. You can also pass custom configuration from the `mount` call from a test, which is accessible from the `hooksConfig` fixture. This includes any config that needs to be run before or after mounting the component. An example of configuring a router is provided below:
@ -423,16 +598,131 @@ You can use `beforeMount` and `afterMount` hooks to configure your app. This let
</Tabs>
## Under the hood
### unmount
Here is how component testing works:
Unmount the mounted component from the DOM. This is useful for testing the component's behavior upon unmounting. Use cases include testing an "Are you sure you want to leave?" modal or ensuring proper cleanup of event handlers to prevent memory leaks.
- Once the tests are executed, Playwright creates a list of components that the tests need.
- It then compiles a bundle that includes these components and serves it using a local static web server.
- Upon the `mount` call within the test, Playwright navigates to the facade page `/playwright/index.html` of this bundle and tells it to render the component.
- Events are marshalled back to the Node.js environment to allow verification.
<Tabs
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
}>
Playwright is using [Vite](https://vitejs.dev/) to create the components bundle and serve it.
<TabItem value="react">
```js
test('unmount', async ({ mount }) => {
const component = await mount(<Component/>);
await component.unmount();
});
```
</TabItem>
<TabItem value="solid">
```js
test('unmount', async ({ mount }) => {
const component = await mount(<Component/>);
await component.unmount();
});
```
</TabItem>
<TabItem value="svelte">
```js
test('unmount', async ({ mount }) => {
const component = await mount(Component);
await component.unmount();
});
```
</TabItem>
<TabItem value="vue">
```js
test('unmount', async ({ mount }) => {
const component = await mount(Component);
await component.unmount();
});
```
</TabItem>
</Tabs>
### update
Update props, slots/children, and/or events/callbacks of a mounted component. These component inputs can change at any time and are typically provided by the parent component, but sometimes it is necessary to ensure that your components behave appropriately to new inputs.
<Tabs
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
}>
<TabItem value="react">
```js
test('update', async ({ mount }) => {
const component = await mount(<Component/>);
await component.update(
<Component msg="greetings" callback={() => {}}>Child</Component>
);
});
```
</TabItem>
<TabItem value="solid">
```js
test('update', async ({ mount }) => {
const component = await mount(<Component/>);
await component.update(
<Component msg="greetings" callback={() => {}}>Child</Component>
);
});
```
</TabItem>
<TabItem value="svelte">
```js
test('update', async ({ mount }) => {
const component = await mount(<Component/>);
await component.update({
props: { msg: 'greetings' },
on: { callback: () => {} },
slots: { default: 'Child' }
});
});
```
</TabItem>
<TabItem value="vue">
```js
test('update', async ({ mount }) => {
const component = await mount(<Component/>);
await component.update({
props: { msg: 'greetings' },
on: { callback: () => {} },
slots: { default: 'Child' }
});
});
```
</TabItem>
</Tabs>
## Frequently asked questions
@ -641,3 +931,7 @@ test('override initialState ', async ({ mount }) => {
await expect(component).toContainText('override initialState');
});
```
### How do I access the component's methods or its instance?
Accessing a component's internal methods or its instance within test code is neither recommended nor supported. Instead, focus on observing and interacting with the component from a user's perspective, typically by clicking or verifying if something is visible on the page. Tests become less fragile and more valuable when they avoid interacting with internal implementation details, such as the component instance or its methods. Keep in mind that if a test fails when run from a users perspective, it likely means the automated test has uncovered a genuine bug in your code.

View file

@ -59,14 +59,14 @@ export default defineConfig({
| Option | Description |
| :- | :- |
| [`property: TestConfig.forbidOnly`] | Whether to exit with an error if any tests are marked as `test.only`. Useful on CI.|
| [`property: TestConfig.fullyParallel`] | have all tests in all files to run in parallel. See [/Parallelism and sharding](./test-parallel) for more details. |
| [`property: TestConfig.fullyParallel`] | have all tests in all files to run in parallel. See [Parallelism](./test-parallel) and [Sharding](./test-sharding) for more details. |
| [`property: TestConfig.projects`] | Run tests in multiple configurations or on multiple browsers |
| [`property: TestConfig.reporter`] | Reporter to use. See [Test Reporters](/test-reporters.md) to learn more about which reporters are available. |
| [`property: TestConfig.retries`] | The maximum number of retry attempts per test. See [Test Retries](/test-retries.md) to learn more about retries.|
| [`property: TestConfig.testDir`] | Directory with the test files. |
| [`property: TestConfig.use`] | Options with `use{}` |
| [`property: TestConfig.webServer`] | To launch a server during the tests, use the `webServer` option |
| [`property: TestConfig.workers`] | The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of logical CPU cores, e.g. `'50%'.`. See [/Parallelism and sharding](./test-parallel) for more details. |
| [`property: TestConfig.workers`] | The maximum number of concurrent worker processes to use for parallelizing tests. Can also be set as percentage of logical CPU cores, e.g. `'50%'.`. See [Parallelism](./test-parallel) and [Sharding](./test-sharding) for more details. |
## Filtering Tests

View file

@ -43,48 +43,7 @@ Here is how typical test environment setup differs between traditional test styl
<summary>Click to expand the code for the <code>TodoPage</code></summary>
<div>
```js tab=js-js title="todo-page.js"
export class TodoPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
this.inputBox = this.page.locator('input.new-todo');
this.todoItems = this.page.getByTestId('todo-item');
}
async goto() {
await this.page.goto('https://demo.playwright.dev/todomvc/');
}
/**
* @param {string} text
*/
async addToDo(text) {
await this.inputBox.fill(text);
await this.inputBox.press('Enter');
}
/**
* @param {string} text
*/
async remove(text) {
const todo = this.todoItems.filter({ hasText: text });
await todo.hover();
await todo.getByLabel('Delete').click();
}
async removeAll() {
while ((await this.todoItems.count()) > 0) {
await this.todoItems.first().hover();
await this.todoItems.getByLabel('Delete').first().click();
}
}
}
```
```js tab=js-ts title="todo-page.ts"
```js title="todo-page.ts"
import type { Page, Locator } from '@playwright/test';
export class TodoPage {
@ -167,48 +126,7 @@ Fixtures have a number of advantages over before/after hooks:
<summary>Click to expand the code for the <code>TodoPage</code></summary>
<div>
```js tab=js-js title="todo-page.js"
export class TodoPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
this.inputBox = this.page.locator('input.new-todo');
this.todoItems = this.page.getByTestId('todo-item');
}
async goto() {
await this.page.goto('https://demo.playwright.dev/todomvc/');
}
/**
* @param {string} text
*/
async addToDo(text) {
await this.inputBox.fill(text);
await this.inputBox.press('Enter');
}
/**
* @param {string} text
*/
async remove(text) {
const todo = this.todoItems.filter({ hasText: text });
await todo.hover();
await todo.getByLabel('Delete').click();
}
async removeAll() {
while ((await this.todoItems.count()) > 0) {
await this.todoItems.first().hover();
await this.todoItems.getByLabel('Delete').first().click();
}
}
}
```
```js tab=js-ts title="todo-page.ts"
```js title="todo-page.ts"
import type { Page, Locator } from '@playwright/test';
export class TodoPage {
@ -246,34 +164,7 @@ export class TodoPage {
</div>
</details>
```js tab=js-js title="todo.spec.js"
const base = require('@playwright/test');
const { TodoPage } = require('./todo-page');
// Extend basic test by providing a "todoPage" fixture.
const test = base.test.extend({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
await use(todoPage);
await todoPage.removeAll();
},
});
test('should add an item', async ({ todoPage }) => {
await todoPage.addToDo('my item');
// ...
});
test('should remove an item', async ({ todoPage }) => {
await todoPage.remove('item1');
// ...
});
```
```js tab=js-ts title="example.spec.ts"
```js title="example.spec.ts"
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
@ -309,48 +200,8 @@ Below we create two fixtures `todoPage` and `settingsPage` that follow the [Page
<details>
<summary>Click to expand the code for the <code>TodoPage</code> and <code>SettingsPage</code></summary>
<div>
```js tab=js-js title="todo-page.js"
export class TodoPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
this.inputBox = this.page.locator('input.new-todo');
this.todoItems = this.page.getByTestId('todo-item');
}
async goto() {
await this.page.goto('https://demo.playwright.dev/todomvc/');
}
/**
* @param {string} text
*/
async addToDo(text) {
await this.inputBox.fill(text);
await this.inputBox.press('Enter');
}
/**
* @param {string} text
*/
async remove(text) {
const todo = this.todoItems.filter({ hasText: text });
await todo.hover();
await todo.getByLabel('Delete').click();
}
async removeAll() {
while ((await this.todoItems.count()) > 0) {
await this.todoItems.first().hover();
await this.todoItems.getByLabel('Delete').first().click();
}
}
}
```
```js tab=js-ts title="todo-page.ts"
```js title="todo-page.ts"
import type { Page, Locator } from '@playwright/test';
export class TodoPage {
@ -388,22 +239,7 @@ export class TodoPage {
SettingsPage is similar:
```js tab=js-js title="settings-page.js"
export class SettingsPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
}
async switchToDarkMode() {
// ...
}
}
```
```js tab=js-ts title="settings-page.ts"
```js title="settings-page.ts"
import type { Page } from '@playwright/test';
export class SettingsPage {
@ -419,36 +255,7 @@ export class SettingsPage {
</div>
</details>
```js tab=js-js title="my-test.js"
const base = require('@playwright/test');
const { TodoPage } = require('./todo-page');
const { SettingsPage } = require('./settings-page');
// Extend base test by providing "todoPage" and "settingsPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
exports.test = base.test.extend({
todoPage: async ({ page }, use) => {
// Set up the fixture.
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
// Use the fixture value in the test.
await use(todoPage);
// Clean up the fixture.
await todoPage.removeAll();
},
settingsPage: async ({ page }, use) => {
await use(new SettingsPage(page));
},
});
exports.expect = base.expect;
```
```js tab=js-ts title="my-test.ts"
```js title="my-test.ts"
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
import { SettingsPage } from './settings-page';
@ -493,20 +300,7 @@ Just mention fixture in your test function argument, and test runner will take c
Below we use the `todoPage` and `settingsPage` fixtures defined above.
```js tab=js-js
const { test, expect } = require('./my-test');
test.beforeEach(async ({ settingsPage }) => {
await settingsPage.switchToDarkMode();
});
test('basic test', async ({ todoPage, page }) => {
await todoPage.addToDo('something nice');
await expect(page.getByTestId('todo-title')).toContainText(['something nice']);
});
```
```js tab=js-ts
```js
import { test, expect } from './my-test';
test.beforeEach(async ({ settingsPage }) => {
@ -560,47 +354,7 @@ Playwright Test uses [worker processes](./test-parallel.md) to run test files. S
Below we'll create an `account` fixture that will be shared by all tests in the same worker, and override the `page` fixture to login into this account for each test. To generate unique accounts, we'll use the [`property: WorkerInfo.workerIndex`] that is available to any test or fixture. Note the tuple-like syntax for the worker fixture - we have to pass `{scope: 'worker'}` so that test runner sets up this fixture once per worker.
```js tab=js-js title="my-test.js"
const base = require('@playwright/test');
exports.test = base.test.extend({
account: [async ({ browser }, use, workerInfo) => {
// Unique username.
const username = 'user' + workerInfo.workerIndex;
const password = 'verysecure';
// Create the account with Playwright.
const page = await browser.newPage();
await page.goto('/signup');
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByText('Sign up').click();
// Make sure everything is ok.
await expect(page.locator('#result')).toHaveText('Success');
// Do not forget to cleanup.
await page.close();
// Use the account value.
await use({ username, password });
}, { scope: 'worker' }],
page: async ({ page, account }, use) => {
// Sign in with our account.
const { username, password } = account;
await page.goto('/signin');
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByText('Sign in').click();
await expect(page.getByTestId('userinfo')).toHaveText(username);
// Use signed-in page in the test.
await use(page);
},
});
exports.expect = base.expect;
```
```js tab=js-ts title="my-test.ts"
```js title="my-test.ts"
import { test as base } from '@playwright/test';
type Account = {
@ -652,32 +406,7 @@ Automatic fixtures are set up for each test/worker, even when the test does not
Here is an example fixture that automatically attaches debug logs when the test fails, so we can later review the logs in the reporter. Note how it uses [TestInfo] object that is available in each test/fixture to retrieve metadata about the test being run.
```js tab=js-js title="my-test.js"
const debug = require('debug');
const fs = require('fs');
const base = require('@playwright/test');
exports.test = base.test.extend({
saveLogs: [async ({}, use, testInfo) => {
// Collecting logs during the test.
const logs = [];
debug.log = (...args) => logs.push(args.map(String).join(''));
debug.enable('myserver');
await use();
// After the test we can check whether the test passed or failed.
if (testInfo.status !== testInfo.expectedStatus) {
// outputPath() API guarantees a unique file name.
const logFile = testInfo.outputPath('logs.txt');
await fs.promises.writeFile(logFile, logs.join('\n'), 'utf8');
testInfo.attachments.push({ name: 'logs', contentType: 'text/plain', path: logFile });
}
}, { auto: true }],
});
```
```js tab=js-ts title="my-test.ts"
```js title="my-test.ts"
import * as debug from 'debug';
import * as fs from 'fs';
import { test as base } from '@playwright/test';
@ -707,22 +436,7 @@ export { expect } from '@playwright/test';
By default, fixture shares timeout with the test. However, for slow fixtures, especially [worker-scoped](#worker-scoped-fixtures) ones, it is convenient to have a separate timeout. This way you can keep the overall test timeout small, and give the slow fixture more time.
```js tab=js-js
const { test: base, expect } = require('@playwright/test');
const test = base.extend({
slowFixture: [async ({}, use) => {
// ... perform a slow operation ...
await use('hello');
}, { timeout: 60000 }]
});
test('example test', async ({ slowFixture }) => {
// ...
});
```
```js tab=js-ts
```js
import { test as base, expect } from '@playwright/test';
const test = base.extend<{ slowFixture: string }>({
@ -752,48 +466,7 @@ Below we'll create a `defaultItem` option in addition to the `todoPage` fixture
<summary>Click to expand the code for the <code>TodoPage</code></summary>
<div>
```js tab=js-js title="todo-page.js"
export class TodoPage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
this.inputBox = this.page.locator('input.new-todo');
this.todoItems = this.page.getByTestId('todo-item');
}
async goto() {
await this.page.goto('https://demo.playwright.dev/todomvc/');
}
/**
* @param {string} text
*/
async addToDo(text) {
await this.inputBox.fill(text);
await this.inputBox.press('Enter');
}
/**
* @param {string} text
*/
async remove(text) {
const todo = this.todoItems.filter({ hasText: text });
await todo.hover();
await todo.getByLabel('Delete').click();
}
async removeAll() {
while ((await this.todoItems.count()) > 0) {
await this.todoItems.first().hover();
await this.todoItems.getByLabel('Delete').first().click();
}
}
}
```
```js tab=js-ts title="todo-page.ts"
```js title="todo-page.ts"
import type { Page, Locator } from '@playwright/test';
export class TodoPage {
@ -832,28 +505,7 @@ export class TodoPage {
</div>
</details>
```js tab=js-js title="my-test.js"
const base = require('@playwright/test');
const { TodoPage } = require('./todo-page');
exports.test = base.test.extend({
// Define an option and provide a default value.
// We can later override it in the config.
defaultItem: ['Something nice', { option: true }],
// Our "todoPage" fixture depends on the option.
todoPage: async ({ page, defaultItem }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo(defaultItem);
await use(todoPage);
await todoPage.removeAll();
},
});
exports.expect = base.expect;
```
```js tab=js-ts title="my-test.ts"
```js title="my-test.ts"
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
@ -885,25 +537,7 @@ export { expect } from '@playwright/test';
We can now use `todoPage` fixture as usual, and set the `defaultItem` option in the config file.
```js tab=js-js title="playwright.config.ts"
// @ts-check
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
projects: [
{
name: 'shopping',
use: { defaultItem: 'Buy milk' },
},
{
name: 'wellbeing',
use: { defaultItem: 'Exercise!' },
},
]
});
```
```js tab=js-ts title="playwright.config.ts"
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
import type { MyOptions } from './my-test';
@ -932,50 +566,7 @@ Fixtures follow these rules to determine the execution order:
Consider the following example:
```js tab=js-js
const { test: base } = require('@playwright/test');
const test = base.extend({
workerFixture: [async ({ browser }) => {
// workerFixture setup...
await use('workerFixture');
// workerFixture teardown...
}, { scope: 'worker' }],
autoWorkerFixture: [async ({ browser }) => {
// autoWorkerFixture setup...
await use('autoWorkerFixture');
// autoWorkerFixture teardown...
}, { scope: 'worker', auto: true }],
testFixture: [async ({ page, workerFixture }) => {
// testFixture setup...
await use('testFixture');
// testFixture teardown...
}, { scope: 'test' }],
autoTestFixture: [async () => {
// autoTestFixture setup...
await use('autoTestFixture');
// autoTestFixture teardown...
}, { scope: 'test', auto: true }],
unusedFixture: [async ({ page }) => {
// unusedFixture setup...
await use('unusedFixture');
// unusedFixture teardown...
}, { scope: 'test' }],
});
test.beforeAll(async () => { /* ... */ });
test.beforeEach(async ({ page }) => { /* ... */ });
test('first test', async ({ page }) => { /* ... */ });
test('second test', async ({ testFixture }) => { /* ... */ });
test.afterEach(async () => { /* ... */ });
test.afterAll(async () => { /* ... */ });
```
```js tab=js-ts
```js
import { test as base } from '@playwright/test';
const test = base.extend<{
@ -1081,3 +672,30 @@ test('passes', async ({ database, page, a11y }) => {
// use database and a11y fixtures.
});
```
## Box fixtures
You can minimize the fixture exposure to the reporters UI and error messages via boxing it:
```js
import { test as base } from '@playwright/test';
export const test = base.extend({
_helperFixture: [async ({}, use, testInfo) => {
}, { box: true }],
});
```
## Custom fixture title
You can assign a custom title to a fixture to be used in error messages and in the
reporters UI:
```js
import { test as base } from '@playwright/test';
export const test = base.extend({
_innerFixture: [async ({}, use, testInfo) => {
}, { title: 'my fixture' }],
});
```

View file

@ -9,13 +9,61 @@ You can either parameterize tests on a test level or on a project level.
## Parameterized Tests
```js title="example.spec.ts"
const people = ['Alice', 'Bob'];
for (const name of people) {
test(`testing with ${name}`, async () => {
[
{ name: 'Alice', expected: 'Hello, Alice!' },
{ name: 'Bob', expected: 'Hello, Bob!' },
{ name: 'Charlie', expected: 'Hello, Charlie!' },
].forEach(({ name, expected }) => {
// You can also do it with test.describe() or with multiple tests as long the test name is unique.
test(`testing with ${name}`, async ({ page }) => {
await page.goto(`https://example.com/greet?name=${name}`);
await expect(page.getByRole('heading')).toHaveText(expected);
});
});
```
### Before and after hooks
Most of the time you should put `beforeEach`, `beforeAll`, `afterEach` and `afterAll` hooks outside of `forEach`, so that hooks are executed just once:
```js title="example.spec.ts"
test.beforeEach(async ({ page }) => {
// ...
});
// You can also do it with test.describe() or with multiple tests as long the test name is unique.
}
test.afterEach(async ({ page }) => {
// ...
});
[
{ name: 'Alice', expected: 'Hello, Alice!' },
{ name: 'Bob', expected: 'Hello, Bob!' },
{ name: 'Charlie', expected: 'Hello, Charlie!' },
].forEach(({ name, expected }) => {
test(`testing with ${name}`, async ({ page }) => {
await page.goto(`https://example.com/greet?name=${name}`);
await expect(page.getByRole('heading')).toHaveText(expected);
});
});
```
If you want to have hooks for each test, you can put them inside a `describe()` - so they are executed for each iteration / each invidual test:
```js title="example.spec.ts"
[
{ name: 'Alice', expected: 'Hello, Alice!' },
{ name: 'Bob', expected: 'Hello, Bob!' },
{ name: 'Charlie', expected: 'Hello, Charlie!' },
].forEach(({ name, expected }) => {
test.describe(() => {
test.beforeEach(async ({ page }) => {
await page.goto(`https://example.com/greet?name=${name}`);
});
test(`testing with ${expected}`, async ({ page }) => {
await expect(page.getByRole('heading')).toHaveText(expected);
});
});
});
```
## Parameterized Projects
@ -216,8 +264,8 @@ import { defineConfig } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
// Read from default ".env" file.
dotenv.config();
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, '.env') });
// Alternatively, read from "../my.env" file.
dotenv.config({ path: path.resolve(__dirname, '..', 'my.env') });

View file

@ -5,135 +5,12 @@ title: "Test Runners"
## Introduction
While Playwright for .NET isn't tied to a particular test runner or testing framework, in our experience it works best with the built-in .NET test runner, and using NUnit as the test framework. NUnit is also what we use internally for [our tests](https://github.com/microsoft/playwright-dotnet/tree/main/src/Playwright.Tests).
While Playwright for .NET isn't tied to a particular test runner or testing framework, in our experience the easiest way of getting started is by using the base classes we provide for [MSTest](#mstest) and [NUnit](#nunit). These classes support running tests on multiple browser engines, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box.
Playwright and Browser instances can be reused between tests for better performance. We
Playwright and Browser instances will be reused between tests for better performance. We
recommend running each test case in a new BrowserContext, this way browser state will be
isolated between the tests.
## NUnit
Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package.
Check out the [installation guide](./intro.md) to get started.
### Running NUnit tests in Parallel
By default NUnit will run all test files in parallel, while running tests inside each file sequentially (`ParallelScope.Self`). It will create as many processes as there are cores on the host system. You can adjust this behavior using the NUnit.NumberOfTestWorkers parameter.
Only `ParallelScope.Self` is supported.
For CPU-bound tests, we recommend using as many workers as there are cores on your system, divided by 2. For IO-bound tests you can use as many workers as you have cores.
```bash
dotnet test -- NUnit.NumberOfTestWorkers=5
```
### Customizing [BrowserContext] options
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
```csharp
using Microsoft.Playwright.NUnit;
namespace PlaywrightTests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class MyTest : PageTest
{
[Test]
public async Task TestWithCustomContextOptions()
{
// The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set:
await Page.GotoAsync("/login");
}
public override BrowserNewContextOptions ContextOptions()
{
return new BrowserNewContextOptions()
{
ColorScheme = ColorScheme.Light,
ViewportSize = new()
{
Width = 1920,
Height = 1080
},
BaseURL = "https://github.com",
};
}
}
```
### Customizing [Browser]/launch options
[Browser]/launch options can be overridden either using a run settings file or by setting the run settings options directly via the
CLI. See the following example:
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<Playwright>
<BrowserName>chromium</BrowserName>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
```bash
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless=false Playwright.LaunchOptions.Channel=msedge
```
### Using Verbose API Logs
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In NUnit, within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
### Using the .runsettings file
When running tests from Visual Studio, you can take advantage of the `.runsettings` file. The following shows a reference of the supported values.
For example, to specify the amount of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`.
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<!-- NUnit adapter -->
<NUnit>
<NumberOfTestWorkers>24</NumberOfTestWorkers>
</NUnit>
<!-- General run configuration -->
<RunConfiguration>
<EnvironmentVariables>
<!-- For debugging selectors, it's recommend to set the following environment variable -->
<DEBUG>pw:api</DEBUG>
</EnvironmentVariables>
</RunConfiguration>
<!-- Playwright -->
<Playwright>
<BrowserName>chromium</BrowserName>
<ExpectTimeout>5000</ExpectTimeout>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
### Base NUnit classes for Playwright
There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace:
|Test |Description|
|--------------|-----------|
|PageTest |Each test gets a fresh copy of a web [Page] created in its own unique [BrowserContext]. Extending this class is the simplest way of writing a fully-functional Playwright test.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|ContextTest |Each test will get a fresh copy of a [BrowserContext]. You can create as many pages in this context as you'd like. Using this test is the easiest way to test multi-page scenarios where you need more than one tab.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
## MSTest
Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package.
@ -259,6 +136,128 @@ There are a few base classes available to you in `Microsoft.Playwright.MSTest` n
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
## NUnit
Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package.
Check out the [installation guide](./intro.md) to get started.
### Running NUnit tests in Parallel
By default NUnit will run all test files in parallel, while running tests inside each file sequentially (`ParallelScope.Self`). It will create as many processes as there are cores on the host system. You can adjust this behavior using the NUnit.NumberOfTestWorkers parameter.
Only `ParallelScope.Self` is supported.
For CPU-bound tests, we recommend using as many workers as there are cores on your system, divided by 2. For IO-bound tests you can use as many workers as you have cores.
```bash
dotnet test -- NUnit.NumberOfTestWorkers=5
```
### Customizing [BrowserContext] options
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
```csharp
using Microsoft.Playwright.NUnit;
namespace PlaywrightTests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class MyTest : PageTest
{
[Test]
public async Task TestWithCustomContextOptions()
{
// The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set:
await Page.GotoAsync("/login");
}
public override BrowserNewContextOptions ContextOptions()
{
return new BrowserNewContextOptions()
{
ColorScheme = ColorScheme.Light,
ViewportSize = new()
{
Width = 1920,
Height = 1080
},
BaseURL = "https://github.com",
};
}
}
```
### Customizing [Browser]/launch options
[Browser]/launch options can be overridden either using a run settings file or by setting the run settings options directly via the
CLI. See the following example:
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<Playwright>
<BrowserName>chromium</BrowserName>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
```bash
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless=false Playwright.LaunchOptions.Channel=msedge
```
### Using Verbose API Logs
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In NUnit, within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
### Using the .runsettings file
When running tests from Visual Studio, you can take advantage of the `.runsettings` file. The following shows a reference of the supported values.
For example, to specify the amount of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`.
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<!-- NUnit adapter -->
<NUnit>
<NumberOfTestWorkers>24</NumberOfTestWorkers>
</NUnit>
<!-- General run configuration -->
<RunConfiguration>
<EnvironmentVariables>
<!-- For debugging selectors, it's recommend to set the following environment variable -->
<DEBUG>pw:api</DEBUG>
</EnvironmentVariables>
</RunConfiguration>
<!-- Playwright -->
<Playwright>
<BrowserName>chromium</BrowserName>
<ExpectTimeout>5000</ExpectTimeout>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
### Base NUnit classes for Playwright
There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace:
|Test |Description|
|--------------|-----------|
|PageTest |Each test gets a fresh copy of a web [Page] created in its own unique [BrowserContext]. Extending this class is the simplest way of writing a fully-functional Playwright test.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|ContextTest |Each test will get a fresh copy of a [BrowserContext]. You can create as many pages in this context as you'd like. Using this test is the easiest way to test multi-page scenarios where you need more than one tab.<br></br><br></br>Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.|
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
## xUnit support
While using xUnit is also supported, we do not support running parallel tests. This is a well known problem/design limitation

View file

@ -5,7 +5,11 @@ title: "Sharding"
## Introduction
By default, Playwright runs test files in [parallel](./test-parallel.md) and strives for optimal utilization of CPU cores on your machine. In order to achieve even greater parallelisation, you can further scale Playwright test execution by running tests on multiple machines simultaneously. We call this mode of operation "sharding".
By default, Playwright runs test files in [parallel](./test-parallel.md) and strives for optimal utilization of CPU cores on your machine. In order to achieve even greater parallelisation, you can further scale Playwright test execution by running tests on multiple machines simultaneously. We call this mode of operation "sharding". Sharding in Playwright means splitting your tests into smaller parts called "shards". Each shard is like a separate job that can run independently. The whole purpose is to divide your tests to speed up test runtime.
When you shard your tests, each shard can run on its own, utilizing the available CPU cores. This helps speed up the testing process by doing tasks simultaneously.
In a CI pipeline, each shard can run as a separate job, making use of the hardware resources available in your CI pipeline, like CPU cores, to run tests faster.
## Sharding tests between multiple machines
@ -18,16 +22,10 @@ npx playwright test --shard=3/4
npx playwright test --shard=4/4
```
Now, if you run these shards in parallel on different computers, your test suite completes four times faster.
Now, if you run these shards in parallel on different jobs, your test suite completes four times faster.
Note that Playwright can only shard tests that can be run in parallel. By default, this means Playwright will shard test files. Learn about other options in the [parallelism guide](./test-parallel.md).
## Randomizing test order in a deterministic way
By default tests are run in the order they are discovered, which is mostly alphabetical. This could lead to an uneven distribution of slow and fast tests. For example, if the first half of your tests are slower than the rest of your tests and you are using 4 shards it means that shard 1 and 2 will take significantly more time then shard 3 and 4.
To aid with this problem you can pass `--sharding-seed=string-value` to randomize the order of tests in a deterministic way, which could yield better distribution of slow and fast tests across all shards.
## Merging reports from multiple shards
In the previous example, each test shard has its own test report. If you want to have a combined report showing all the test results from all the shards, you can merge them.

View file

@ -108,16 +108,3 @@ In `package.json`, add two scripts:
The `pretest` script runs typescript on the tests. `test` will run the tests that have been generated to the `tests-out` directory. The `-c` argument configures the test runner to look for tests inside the `tests-out` directory.
Then `npm run test` will build the tests and run them.
## Using `import` inside `evaluate()`
Using dynamic imports inside a function passed to various `evaluate()` methods is not supported. This is because Playwright uses `Function.prototype.toString()` to serialize functions, and transpiler will sometimes replace dynamic imports with `require()` calls, which are not valid inside the web page.
To work around this issue, use a string template instead of a function:
```js
await page.evaluate(`(async () => {
const { value } = await import('some-module');
console.log(value);
})()`);
```

View file

@ -14,19 +14,24 @@ If you use DOM Testing Library in the browser (for example, you bundle end-to-en
## Cheat Sheet
| Testing Library | Playwright |
|---------------------------------------------------------|-----------------------------------------------|
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [screen](https://testing-library.com/docs/queries/about#screen) | [page](./api/class-page) and [component](./api/class-locator) |
| [queries](https://testing-library.com/docs/queries/about) | [locators](./locators) |
| [async helpers](https://testing-library.com/docs/dom-testing-library/api-async) | [assertions](./test-assertions) |
| [user events](https://testing-library.com/docs/user-event/intro) | [actions](./api/class-locator) |
| `await user.click(screen.getByText('Click me'))` | `await component.getByText('Click me').click()` |
| `await user.click(await screen.findByText('Click me'))` | `await component.getByText('Click me').click()` |
| `await user.type(screen.getByLabel('Password'), 'secret')` | `await component.getByLabel('Password').fill('secret')` |
| `expect(screen.getByLabel('Password')).toHaveValue('secret')` | `await expect(component.getByLabel('Password')).toHaveValue('secret')` |
| `await user.type(screen.getByLabelText('Password'), 'secret')` | `await component.getByLabel('Password').fill('secret')` |
| `expect(screen.getByLabelText('Password')).toHaveValue('secret')` | `await expect(component.getByLabel('Password')).toHaveValue('secret')` |
| `screen.getByRole('button', { pressed: true })` | `component.getByRole('button', { pressed: true })` |
| `screen.getByLabelText('...')` | `component.getByLabel('...')` |
| `screen.queryByPlaceholderText('...')` | `component.getByPlaceholder('...')` |
| `screen.findByText('...')` | `component.getByText('...')` |
| `screen.getByTestId('...')` | `component.getByTestId('...')` |
| `screen.queryByPlaceholderText('...')` | `component.getByPlaceholder('...')` |
| `screen.getByRole('button', { pressed: true })` | `component.getByRole('button', { pressed: true })`|
| `render(<Component />);` | `mount(<Component />);` |
| `const { unmount } = render(<Component />);` | `const { unmount } = await mount(<Component />);` |
| `const { rerender } = render(<Component />);` | `const { update } = await mount(<Component />);` |
## Example
@ -37,18 +42,18 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('should sign in', async () => {
test('sign in', async () => {
// Setup the page.
const user = userEvent.setup();
render(<SignInPage />);
// Perform actions.
await user.type(screen.getByLabel('Username'), 'John');
await user.type(screen.getByLabel('Password'), 'secret');
await user.click(screen.getByText('Sign in'));
await user.type(screen.getByLabelText('Username'), 'John');
await user.type(screen.getByLabelText('Password'), 'secret');
await user.click(screen.getByRole('button', { name: 'Sign in' }));
// Verify signed in state by waiting until "Welcome" message appears.
await screen.findByText('Welcome, John');
expect(await screen.findByText('Welcome, John')).toBeInTheDocument();
});
```
@ -57,14 +62,14 @@ Line-by-line migration to Playwright Test:
```js
const { test, expect } = require('@playwright/experimental-ct-react'); // 1
test('should sign in', async ({ page, mount }) => { // 2
test('sign in', async ({ mount }) => { // 2
// Setup the page.
const component = await mount(<SignInPage />); // 3
// Perform actions.
await component.getByText('Username').fill('John'); // 4
await component.getByText('Password').fill('secret');
await component.getByText('Sign in').click();
await component.getByLabel('Username').fill('John'); // 4
await component.getByLabel('Password').fill('secret');
await component.getByRole('button', { name: 'Sign in' }).click();
// Verify signed in state by waiting until "Welcome" message appears.
await expect(component.getByText('Welcome, John')).toBeVisible(); // 5
@ -118,7 +123,7 @@ const messages = document.getElementById('messages');
const helloMessage = within(messages).getByText('hello');
// Playwright
const messages = component.locator('id=messages');
const messages = component.getByTestId('messages');
const helloMessage = messages.getByText('hello');
```
@ -133,7 +138,9 @@ Once you're on Playwright Test, you get a lot!
- Built-in test [artifact collection](./test-use-options.md#recording-options)
You also get all these ✨ awesome tools ✨ that come bundled with Playwright Test:
- [Playwright Inspector](./debug.md)
- [Visual Studio Code integration](./getting-started-vscode.md)
- [UI mode](./test-ui-mode.md) for debugging tests with a time travel experience complete with watch mode.
- [Playwright Inspector](./debug.md#playwright-inspector)
- [Playwright Test Code generation](./codegen-intro.md)
- [Playwright Tracing](./trace-viewer.md) for post-mortem debugging

View file

@ -18,10 +18,10 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -134,4 +134,4 @@ Check out our detailed guide on [Trace Viewer](/trace-viewer.md) to learn more a
## What's next
- [Run tests on CI with GitHub Actions](/ci-intro.md)
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)

View file

@ -31,7 +31,7 @@ In the Actions tab you can see what locator was used for every action and how lo
### Screenshots
When tracing with the [`option: screenshots`] option turned on, each trace records a screencast and renders it as a film strip. You can hover over the film strip to see a magnified image of for each action and state which helps you easily find the action you want to inspect.
When tracing with the [`option: screenshots`] option turned on (default), each trace records a screencast and renders it as a film strip. You can hover over the film strip to see a magnified image of for each action and state which helps you easily find the action you want to inspect.
Double click on an action to see the time range for that action. You can use the slider in the timeline to increase the actions selected and these will be shown in the Actions tab and all console logs and network logs will be filtered to only show the logs for the actions selected.
@ -58,7 +58,7 @@ Notice how it highlights both, the DOM Node as well as the exact click position.
### Source
As you hover over each action of your test the line of code for that action is highlighted in the source panel.
When you click on an action in the sidebar, the line of code for that action is highlighted in the source panel.
![showing source code tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/daa8845d-c250-4923-aa7a-5d040da9adc5)
@ -86,6 +86,10 @@ See console logs from the browser as well as from your test. Different icons are
![showing log of tests in trace viewer](https://github.com/microsoft/playwright/assets/13063165/4107c08d-1eaf-421c-bdd4-9dd2aa641d4a)
Double click on an action from your test in the actions sidebar. This will filter the console to only show the logs that were made during that action. Click the *Show all* button to see all console logs again.
Use the timeline to filter actions, by clicking a start point and dragging to an ending point. The console tab will also be filtered to only show the logs that were made during the actions selected.
### Network
@ -93,6 +97,10 @@ The Network tab shows you all the network requests that were made during your te
![network requests tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/0a3d1671-8ccd-4f7a-a844-35f5eb37f236)
Double click on an action from your test in the actions sidebar. This will filter the network requests to only show the requests that were made during that action. Click the *Show all* button to see all network requests again.
Use the timeline to filter actions, by clicking a start point and dragging to an ending point. The network tab will also be filtered to only show the network requests that were made during the actions selected.
### Metadata
Next to the Actions tab you will find the Metadata tab which will show you more information on your test such as the Browser, viewport size, test duration and more.
@ -160,6 +168,8 @@ Available options to record a trace:
You can also use `trace: 'retain-on-failure'` if you do not enable retries but still want traces for failed tests.
There are more granular options available, see [`property: TestOptions.trace`].
If you are not using Playwright as a Test Runner, use the [`property: BrowserContext.tracing`] API instead.
## Recording a trace
@ -242,10 +252,10 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -353,10 +363,10 @@ Setup your tests to record a trace only when the test fails:
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">

View file

@ -347,14 +347,14 @@ def test_webview2(page: Page):
```csharp
// WebView2Test.cs
using System.Text.RegularExpressions;
using Microsoft.Playwright.NUnit;
using Microsoft.Playwright;
using System.Diagnostics;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace dotnet_nunit;
namespace PlaywrightTests;
public class WebView2Test : PlaywrightTest
[TestClass]
public class ExampleTest : PlaywrightTest
{
public IBrowser Browser { get; internal set; } = null!;
public IBrowserContext Context { get; internal set; } = null!;
@ -363,12 +363,12 @@ public class WebView2Test : PlaywrightTest
private string _userDataDir = null!;
private string _executablePath = Path.Join(Directory.GetCurrentDirectory(), @"..\..\..\..\webview2-app\bin\Debug\net8.0-windows\webview2.exe");
[SetUp]
public async Task BrowserSetUp()
[TestInitialize]
public async Task BrowserTestInitialize()
{
var cdpPort = 10000 + WorkerIndex;
Assert.IsTrue(File.Exists(_executablePath), "Make sure that the executable exists");
_userDataDir = Path.Join(Path.GetTempPath(), $"playwright-webview2-tests/user-data-dir-{TestContext.CurrentContext.WorkerId}");
_userDataDir = Path.Join(Path.GetTempPath(), $"playwright-webview2-tests/user-data-dir-{WorkerIndex}");
// WebView2 does some lazy cleanups on shutdown so we can't clean it up after each test
if (Directory.Exists(_userDataDir))
{
@ -401,8 +401,8 @@ public class WebView2Test : PlaywrightTest
Page = Context.Pages[0];
}
[TearDown]
public async Task BrowserTearDown()
[TestCleanup]
public async Task BrowserTestCleanup()
{
_webView2Process!.Kill(true);
await Browser.CloseAsync();
@ -412,14 +412,15 @@ public class WebView2Test : PlaywrightTest
```csharp
// UnitTest1.cs
using Microsoft.Playwright.NUnit;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace dotnet_nunit;
namespace PlaywrightTests;
[Parallelizable(ParallelScope.Self)]
public class Tests : WebView2Test
[TestClass]
public class ExampleTest : WebView2Test
{
[Test]
[TestMethod]
public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage()
{
await Page.GotoAsync("https://playwright.dev");

View file

@ -35,10 +35,10 @@ Take a look at the following example to see how to write a test.
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -200,10 +200,10 @@ The Playwright NUnit and MSTest test framework base classes will isolate each te
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -257,10 +257,10 @@ You can use `SetUp`/`TearDown` in NUnit or `TestInitialize`/`TestCleanup` in MST
<Tabs
groupId="test-runners"
defaultValue="nunit"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'MSTest', value: 'mstest'}
]
}>
<TabItem value="nunit">
@ -328,4 +328,4 @@ public class ExampleTest : PageTest
- [Generate tests with Codegen](./codegen-intro.md)
- [See a trace of your tests](./trace-viewer-intro.md)
- [Run tests on CI](./ci-intro.md)
- [Learn more about the NUnit and MSTest base classes](./test-runners.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)

630
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "playwright-internal",
"private": true,
"version": "1.45.0-next",
"version": "1.46.0-next",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",
@ -82,7 +82,7 @@
"concurrently": "^6.2.1",
"cross-env": "^7.0.3",
"dotenv": "^16.0.0",
"electron": "19.0.11",
"electron": "^30.1.2",
"enquirer": "^2.3.6",
"esbuild": "^0.18.11",
"eslint": "^8.55.0",
@ -100,7 +100,7 @@
"ssim.js": "^3.5.0",
"typescript": "^5.3.2",
"vite": "^5.0.13",
"ws": "^8.5.0",
"ws": "^8.17.1",
"xml2js": "^0.5.0",
"yaml": "^2.2.2"
}

View file

@ -52,8 +52,8 @@ const testCase: TestCase = {
projectName: 'chromium',
location: { file: 'test.spec.ts', line: 42, column: 0 },
annotations: [
{ type: 'annotation', description: 'Annotation text', url: 'example url' },
{ type: 'annotation', description: 'Another annotation text', url: 'Another example url' },
{ type: 'annotation', description: 'Annotation text' },
{ type: 'annotation', description: 'Another annotation text' },
],
tags: [],
outcome: 'expected',

View file

@ -59,7 +59,7 @@ export type TestFileSummary = {
stats: Stats;
};
export type TestCaseAnnotation = { type: string, description?: string, url?: string};
export type TestCaseAnnotation = { type: string, description?: string };
export type TestCaseSummary = {
testId: string,

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/browser-chromium",
"version": "1.45.0-next",
"version": "1.46.0-next",
"description": "Playwright package that automatically installs Chromium",
"repository": {
"type": "git",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.45.0-next"
"playwright-core": "1.46.0-next"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/browser-firefox",
"version": "1.45.0-next",
"version": "1.46.0-next",
"description": "Playwright package that automatically installs Firefox",
"repository": {
"type": "git",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.45.0-next"
"playwright-core": "1.46.0-next"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/browser-webkit",
"version": "1.45.0-next",
"version": "1.46.0-next",
"description": "Playwright package that automatically installs WebKit",
"repository": {
"type": "git",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.45.0-next"
"playwright-core": "1.46.0-next"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright-chromium",
"version": "1.45.0-next",
"version": "1.46.0-next",
"description": "A high-level API to automate Chromium",
"repository": {
"type": "git",
@ -30,6 +30,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.45.0-next"
"playwright-core": "1.46.0-next"
}
}

View file

@ -23,10 +23,11 @@ This project incorporates components from the projects listed below. The origina
- get-stream@5.2.0 (https://github.com/sindresorhus/get-stream)
- graceful-fs@4.2.10 (https://github.com/isaacs/node-graceful-fs)
- https-proxy-agent@5.0.0 (https://github.com/TooTallNate/node-https-proxy-agent)
- ip@2.0.1 (https://github.com/indutny/node-ip)
- ip-address@9.0.5 (https://github.com/beaugunderson/ip-address)
- is-docker@2.2.1 (https://github.com/sindresorhus/is-docker)
- is-wsl@2.2.0 (https://github.com/sindresorhus/is-wsl)
- jpeg-js@0.4.4 (https://github.com/eugeneware/jpeg-js)
- jsbn@1.1.0 (https://github.com/andyperlitch/jsbn)
- mime@3.0.0 (https://github.com/broofa/mime)
- minimatch@3.1.2 (https://github.com/isaacs/minimatch)
- ms@2.1.2 (https://github.com/zeit/ms)
@ -41,10 +42,11 @@ This project incorporates components from the projects listed below. The origina
- signal-exit@3.0.7 (https://github.com/tapjs/signal-exit)
- smart-buffer@4.2.0 (https://github.com/JoshGlazebrook/smart-buffer)
- socks-proxy-agent@6.1.1 (https://github.com/TooTallNate/node-socks-proxy-agent)
- socks@2.7.0 (https://github.com/JoshGlazebrook/socks)
- socks@2.8.3 (https://github.com/JoshGlazebrook/socks)
- sprintf-js@1.1.3 (https://github.com/alexei/sprintf.js)
- stack-utils@2.0.5 (https://github.com/tapjs/stack-utils)
- wrappy@1.0.2 (https://github.com/npm/wrappy)
- ws@8.4.2 (https://github.com/websockets/ws)
- ws@8.17.1 (https://github.com/websockets/ws)
- yauzl@2.10.0 (https://github.com/thejoshwolfe/yauzl)
- yazl@2.5.1 (https://github.com/thejoshwolfe/yazl)
@ -740,100 +742,29 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF https-proxy-agent@5.0.0 AND INFORMATION
%% ip@2.0.1 NOTICES AND INFORMATION BEGIN HERE
%% ip-address@9.0.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
# IP
[![](https://badge.fury.io/js/ip.svg)](https://www.npmjs.com/package/ip)
Copyright (C) 2011 by Beau Gunderson
IP address utilities for node.js
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
## Installation
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
### npm
```shell
npm install ip
```
### git
```shell
git clone https://github.com/indutny/node-ip.git
```
## Usage
Get your ip address, compare ip addresses, validate ip addresses, etc.
```js
var ip = require('ip');
ip.address() // my ip address
ip.isEqual('::1', '::0:1'); // true
ip.toBuffer('127.0.0.1') // Buffer([127, 0, 0, 1])
ip.toString(new Buffer([127, 0, 0, 1])) // 127.0.0.1
ip.fromPrefixLen(24) // 255.255.255.0
ip.mask('192.168.1.134', '255.255.255.0') // 192.168.1.0
ip.cidr('192.168.1.134/26') // 192.168.1.128
ip.not('255.255.255.0') // 0.0.0.255
ip.or('192.168.1.134', '0.0.0.255') // 192.168.1.255
ip.isPrivate('127.0.0.1') // true
ip.isV4Format('127.0.0.1'); // true
ip.isV6Format('::ffff:127.0.0.1'); // true
// operate on buffers in-place
var buf = new Buffer(128);
var offset = 64;
ip.toBuffer('127.0.0.1', buf, offset); // [127, 0, 0, 1] at offset 64
ip.toString(buf, offset, 4); // '127.0.0.1'
// subnet information
ip.subnet('192.168.1.134', '255.255.255.192')
// { networkAddress: '192.168.1.128',
// firstAddress: '192.168.1.129',
// lastAddress: '192.168.1.190',
// broadcastAddress: '192.168.1.191',
// subnetMask: '255.255.255.192',
// subnetMaskLength: 26,
// numHosts: 62,
// length: 64,
// contains: function(addr){...} }
ip.cidrSubnet('192.168.1.134/26')
// Same as previous.
// range checking
ip.cidrSubnet('192.168.1.134/26').contains('192.168.1.190') // true
// ipv4 long conversion
ip.toLong('127.0.0.1'); // 2130706433
ip.fromLong(2130706433); // '127.0.0.1'
```
### License
This software is licensed under the MIT License.
Copyright Fedor Indutny, 2012.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF ip@2.0.1 AND INFORMATION
END OF ip-address@9.0.5 AND INFORMATION
%% is-docker@2.2.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -893,6 +824,51 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=========================================
END OF jpeg-js@0.4.4 AND INFORMATION
%% jsbn@1.1.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
Licensing
---------
This software is covered under the following copyright:
/*
* Copyright (c) 2003-2005 Tom Wu
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
* THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* In addition, the following condition applies:
*
* All redistributions must retain an intact copy of this copyright notice
* and disclaimer.
*/
Address all questions regarding this license to:
Tom Wu
tjw@cs.Stanford.EDU
=========================================
END OF jsbn@1.1.0 AND INFORMATION
%% mime@3.0.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
@ -1359,7 +1335,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF socks-proxy-agent@6.1.1 AND INFORMATION
%% socks@2.7.0 NOTICES AND INFORMATION BEGIN HERE
%% socks@2.8.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
@ -1382,7 +1358,36 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF socks@2.7.0 AND INFORMATION
END OF socks@2.8.3 AND INFORMATION
%% sprintf-js@1.1.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
Copyright (c) 2007-present, Alexandru Mărășteanu <hello@alexei.ro>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of this software nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=========================================
END OF sprintf-js@1.1.3 AND INFORMATION
%% stack-utils@2.0.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -1430,29 +1435,30 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
=========================================
END OF wrappy@1.0.2 AND INFORMATION
%% ws@8.4.2 NOTICES AND INFORMATION BEGIN HERE
%% ws@8.17.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
Copyright (c) 2013 Arnout Kazemier and contributors
Copyright (c) 2016 Luigi Pinca and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF ws@8.4.2 AND INFORMATION
END OF ws@8.17.1 AND INFORMATION
%% yauzl@2.10.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -1508,6 +1514,6 @@ END OF yazl@2.5.1 AND INFORMATION
SUMMARY BEGIN HERE
=========================================
Total Packages: 43
Total Packages: 45
=========================================
END OF SUMMARY

View file

@ -3,31 +3,31 @@
"browsers": [
{
"name": "chromium",
"revision": "1121",
"revision": "1125",
"installByDefault": true,
"browserVersion": "126.0.6478.26"
"browserVersion": "127.0.6533.26"
},
{
"name": "chromium-tip-of-tree",
"revision": "1227",
"revision": "1236",
"installByDefault": false,
"browserVersion": "127.0.6510.0"
"browserVersion": "128.0.6561.0"
},
{
"name": "firefox",
"revision": "1452",
"revision": "1456",
"installByDefault": true,
"browserVersion": "126.0"
"browserVersion": "127.0"
},
{
"name": "firefox-beta",
"revision": "1452",
"revision": "1456",
"installByDefault": false,
"browserVersion": "127.0b3"
"browserVersion": "128.0b3"
},
{
"name": "webkit",
"revision": "2014",
"revision": "2040",
"installByDefault": true,
"revisionOverrides": {
"mac10.14": "1446",
@ -46,7 +46,7 @@
},
{
"name": "android",
"revision": "1000",
"revision": "1001",
"installByDefault": false
}
]

View file

@ -24,7 +24,7 @@
"signal-exit": "3.0.7",
"socks-proxy-agent": "6.1.1",
"stack-utils": "2.0.5",
"ws": "8.4.2"
"ws": "8.17.1"
},
"devDependencies": {
"@types/debug": "^4.1.7",
@ -223,10 +223,17 @@
"node": ">= 6"
}
},
"node_modules/ip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
@ -258,6 +265,11 @@
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="
},
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
@ -345,15 +357,15 @@
}
},
"node_modules/socks": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
"integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dependencies": {
"ip": "^2.0.0",
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.13.0",
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
@ -370,6 +382,11 @@
"node": ">= 10"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
},
"node_modules/stack-utils": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz",
@ -382,15 +399,15 @@
}
},
"node_modules/ws": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz",
"integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
@ -562,10 +579,14 @@
"debug": "4"
}
},
"ip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ=="
"ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"requires": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
}
},
"is-docker": {
"version": "2.2.1",
@ -585,6 +606,11 @@
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="
},
"jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
@ -644,11 +670,11 @@
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
},
"socks": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz",
"integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"requires": {
"ip": "^2.0.0",
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
}
},
@ -662,6 +688,11 @@
"socks": "^2.6.1"
}
},
"sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
},
"stack-utils": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz",
@ -671,9 +702,9 @@
}
},
"ws": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz",
"integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"requires": {}
}
}

View file

@ -25,7 +25,7 @@
"signal-exit": "3.0.7",
"socks-proxy-agent": "6.1.1",
"stack-utils": "2.0.5",
"ws": "8.4.2"
"ws": "8.17.1"
},
"devDependencies": {
"@types/debug": "^4.1.7",

View file

@ -1,6 +1,6 @@
{
"name": "playwright-core",
"version": "1.45.0-next",
"version": "1.46.0-next",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",

View file

@ -15,7 +15,6 @@
*/
import type * as api from '../../types/types';
import type * as channels from '@protocol/channels';
import type { BrowserContext } from './browserContext';
export class Clock implements api.Clock {
@ -25,33 +24,48 @@ export class Clock implements api.Clock {
this._browserContext = browserContext;
}
async install(options?: Omit<channels.BrowserContextClockInstallOptions, 'now'> & { now?: number | Date }) {
const now = options && options.now ? (options.now instanceof Date ? options.now.getTime() : options.now) : undefined;
await this._browserContext._channel.clockInstall({ ...options, now });
async install(options: { time?: number | string | Date } = { }) {
await this._browserContext._channel.clockInstall(options.time !== undefined ? parseTime(options.time) : {});
}
async jump(time: number | string) {
await this._browserContext._channel.clockJump({
timeNumber: typeof time === 'number' ? time : undefined,
timeString: typeof time === 'string' ? time : undefined
});
async fastForward(ticks: number | string) {
await this._browserContext._channel.clockFastForward(parseTicks(ticks));
}
async runAll(): Promise<number> {
const result = await this._browserContext._channel.clockRunAll();
return result.fakeTime;
async pauseAt(time: number | string | Date) {
await this._browserContext._channel.clockPauseAt(parseTime(time));
}
async runToLast(): Promise<number> {
const result = await this._browserContext._channel.clockRunToLast();
return result.fakeTime;
async resume() {
await this._browserContext._channel.clockResume({});
}
async tick(time: number | string): Promise<number> {
const result = await this._browserContext._channel.clockTick({
timeNumber: typeof time === 'number' ? time : undefined,
timeString: typeof time === 'string' ? time : undefined
});
return result.fakeTime;
async runFor(ticks: number | string) {
await this._browserContext._channel.clockRunFor(parseTicks(ticks));
}
async setFixedTime(time: string | number | Date) {
await this._browserContext._channel.clockSetFixedTime(parseTime(time));
}
async setSystemTime(time: string | number | Date) {
await this._browserContext._channel.clockSetSystemTime(parseTime(time));
}
}
function parseTime(time: string | number | Date): { timeNumber?: number, timeString?: string } {
if (typeof time === 'number')
return { timeNumber: time };
if (typeof time === 'string')
return { timeString: time };
if (!isFinite(time.getTime()))
throw new Error(`Invalid date: ${time}`);
return { timeNumber: time.getTime() };
}
function parseTicks(ticks: string | number): { ticksNumber?: number, ticksString?: string } {
return {
ticksNumber: typeof ticks === 'number' ? ticks : undefined,
ticksString: typeof ticks === 'string' ? ticks : undefined
};
}

View file

@ -250,12 +250,31 @@ export function convertSelectOptionValues(values: string | api.ElementHandle | S
return { options: values as SelectOption[] };
}
type SetInputFilesFiles = Pick<channels.ElementHandleSetInputFilesParams, 'payloads' | 'localPaths' | 'streams'>;
type SetInputFilesFiles = Pick<channels.ElementHandleSetInputFilesParams, 'payloads' | 'localPaths' | 'localDirectory' | 'streams' | 'directoryStream'>;
function filePayloadExceedsSizeLimit(payloads: FilePayload[]) {
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
}
async function resolvePathsAndDirectoryForInputFiles(items: string[]): Promise<[string[] | undefined, string | undefined]> {
let localPaths: string[] | undefined;
let localDirectory: string | undefined;
for (const item of items) {
const stat = await fs.promises.stat(item as string);
if (stat.isDirectory()) {
if (localDirectory)
throw new Error('Multiple directories are not supported');
localDirectory = path.resolve(item as string);
} else {
localPaths ??= [];
localPaths.push(path.resolve(item as string));
}
}
if (localPaths?.length && localDirectory)
throw new Error('File paths must be all files or a single directory');
return [localPaths, localDirectory];
}
export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise<SetInputFilesFiles> {
const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files];
@ -263,17 +282,33 @@ export async function convertInputFiles(files: string | FilePayload | string[] |
if (!items.every(item => typeof item === 'string'))
throw new Error('File paths cannot be mixed with buffers');
const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(items as string[]);
if (context._connection.isRemote()) {
const streams: channels.WritableStreamChannel[] = await Promise.all((items as string[]).map(async item => {
const lastModifiedMs = (await fs.promises.stat(item)).mtimeMs;
const { writableStream: stream } = await context._wrapApiCall(() => context._channel.createTempFile({ name: path.basename(item), lastModifiedMs }), true);
const writable = WritableStream.from(stream);
await pipelineAsync(fs.createReadStream(item), writable.stream());
return stream;
}));
return { streams };
const files = localDirectory ? (await fs.promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => path.join(f.path, f.name)) : localPaths!;
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
rootDirName: localDirectory ? path.basename(localDirectory as string) : undefined,
items: await Promise.all(files.map(async file => {
const lastModifiedMs = (await fs.promises.stat(file)).mtimeMs;
return {
name: localDirectory ? path.relative(localDirectory as string, file) : path.basename(file),
lastModifiedMs
};
})),
}), true);
for (let i = 0; i < files.length; i++) {
const writable = WritableStream.from(writableStreams[i]);
await pipelineAsync(fs.createReadStream(files[i]), writable.stream());
}
return { localPaths: items.map(f => path.resolve(f as string)) as string[] };
return {
directoryStream: rootDir,
streams: localDirectory ? undefined : writableStreams,
};
}
return {
localPaths,
localDirectory,
};
}
const payloads = items as FilePayload[];

View file

@ -41,6 +41,7 @@ export type FetchOptions = {
failOnStatusCode?: boolean,
ignoreHTTPSErrors?: boolean,
maxRedirects?: number,
maxRetries?: number,
};
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState' | 'tracesDir'> & {
@ -168,11 +169,11 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
throw new TargetClosedError(this._closeReason);
assert(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
assert((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
assert(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
assert(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
const url = options.url !== undefined ? options.url : options.request!.url();
const params = objectToArray(options.params);
const method = options.method || options.request?.method();
const maxRedirects = options.maxRedirects;
// Cannot call allHeaders() here as the request may be paused inside route handler.
const headersObj = options.headers || options.request?.headers() ;
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
@ -234,7 +235,8 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
timeout: options.timeout,
failOnStatusCode: options.failOnStatusCode,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
maxRedirects: maxRedirects,
maxRedirects: options.maxRedirects,
maxRetries: options.maxRetries,
...fixtures
});
return new APIResponse(this, result.response);

View file

@ -189,7 +189,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
async _evaluateExposeUtilityScript<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', exposeUtilityScript: true, arg: serializeArgument(arg) });
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
}

View file

@ -89,4 +89,8 @@ export class Touchscreen implements api.Touchscreen {
async tap(x: number, y: number) {
await this._page._channel.touchscreenTap({ x, y });
}
async touch(type: 'touchstart'|'touchmove'|'touchend'|'touchcancel', touchPoints: { x: number, y: number, id?: number }[]) {
await this._page._channel.touchscreenTouch({ type, touchPoints });
}
}

View file

@ -336,7 +336,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
});
}
async fetch(options: FallbackOverrides & { maxRedirects?: number, timeout?: number } = {}): Promise<APIResponse> {
async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number } = {}): Promise<APIResponse> {
return await this._wrapApiCall(async () => {
return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
});

View file

@ -31,6 +31,7 @@ export const slowMoActions = new Set([
'Page.mouseClick',
'Page.mouseWheel',
'Page.touchscreenTap',
'Page.touchscreenTouch',
'Frame.blur',
'Frame.check',
'Frame.click',
@ -89,6 +90,7 @@ export const commandsWithTracingSnapshots = new Set([
'Page.mouseClick',
'Page.mouseWheel',
'Page.touchscreenTap',
'Page.touchscreenTouch',
'Frame.evalOnSelector',
'Frame.evalOnSelectorAll',
'Frame.addScriptTag',

View file

@ -182,6 +182,7 @@ scheme.APIRequestContextFetchParams = tObject({
failOnStatusCode: tOptional(tBoolean),
ignoreHTTPSErrors: tOptional(tBoolean),
maxRedirects: tOptional(tNumber),
maxRetries: tOptional(tNumber),
});
scheme.APIRequestContextFetchResult = tObject({
response: tType('APIResponse'),
@ -951,46 +952,54 @@ scheme.BrowserContextHarExportParams = tObject({
scheme.BrowserContextHarExportResult = tObject({
artifact: tChannel(['Artifact']),
});
scheme.BrowserContextCreateTempFileParams = tObject({
scheme.BrowserContextCreateTempFilesParams = tObject({
rootDirName: tOptional(tString),
items: tArray(tObject({
name: tString,
lastModifiedMs: tOptional(tNumber),
})),
});
scheme.BrowserContextCreateTempFileResult = tObject({
writableStream: tChannel(['WritableStream']),
scheme.BrowserContextCreateTempFilesResult = tObject({
rootDir: tOptional(tChannel(['WritableStream'])),
writableStreams: tArray(tChannel(['WritableStream'])),
});
scheme.BrowserContextUpdateSubscriptionParams = tObject({
event: tEnum(['console', 'dialog', 'request', 'response', 'requestFinished', 'requestFailed']),
enabled: tBoolean,
});
scheme.BrowserContextUpdateSubscriptionResult = tOptional(tObject({}));
scheme.BrowserContextClockFastForwardParams = tObject({
ticksNumber: tOptional(tNumber),
ticksString: tOptional(tString),
});
scheme.BrowserContextClockFastForwardResult = tOptional(tObject({}));
scheme.BrowserContextClockInstallParams = tObject({
now: tOptional(tNumber),
toFake: tOptional(tArray(tString)),
loopLimit: tOptional(tNumber),
shouldAdvanceTime: tOptional(tBoolean),
advanceTimeDelta: tOptional(tNumber),
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
scheme.BrowserContextClockInstallResult = tOptional(tObject({}));
scheme.BrowserContextClockJumpParams = tObject({
scheme.BrowserContextClockPauseAtParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
scheme.BrowserContextClockJumpResult = tOptional(tObject({}));
scheme.BrowserContextClockRunAllParams = tOptional(tObject({}));
scheme.BrowserContextClockRunAllResult = tObject({
fakeTime: tNumber,
scheme.BrowserContextClockPauseAtResult = tOptional(tObject({}));
scheme.BrowserContextClockResumeParams = tOptional(tObject({}));
scheme.BrowserContextClockResumeResult = tOptional(tObject({}));
scheme.BrowserContextClockRunForParams = tObject({
ticksNumber: tOptional(tNumber),
ticksString: tOptional(tString),
});
scheme.BrowserContextClockRunToLastParams = tOptional(tObject({}));
scheme.BrowserContextClockRunToLastResult = tObject({
fakeTime: tNumber,
});
scheme.BrowserContextClockTickParams = tObject({
scheme.BrowserContextClockRunForResult = tOptional(tObject({}));
scheme.BrowserContextClockSetFixedTimeParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
scheme.BrowserContextClockTickResult = tObject({
fakeTime: tNumber,
scheme.BrowserContextClockSetFixedTimeResult = tOptional(tObject({}));
scheme.BrowserContextClockSetSystemTimeParams = tObject({
timeNumber: tOptional(tNumber),
timeString: tOptional(tString),
});
scheme.BrowserContextClockSetSystemTimeResult = tOptional(tObject({}));
scheme.PageInitializer = tObject({
mainFrame: tChannel(['Frame']),
viewportSize: tOptional(tObject({
@ -1228,6 +1237,15 @@ scheme.PageTouchscreenTapParams = tObject({
y: tNumber,
});
scheme.PageTouchscreenTapResult = tOptional(tObject({}));
scheme.PageTouchscreenTouchParams = tObject({
type: tEnum(['touchstart', 'touchend', 'touchmove', 'touchcancel']),
touchPoints: tArray(tObject({
x: tNumber,
y: tNumber,
id: tOptional(tNumber),
})),
});
scheme.PageTouchscreenTouchResult = tOptional(tObject({}));
scheme.PageAccessibilitySnapshotParams = tObject({
interestingOnly: tOptional(tBoolean),
root: tOptional(tChannel(['ElementHandle'])),
@ -1425,7 +1443,6 @@ scheme.FrameDispatchEventResult = tOptional(tObject({}));
scheme.FrameEvaluateExpressionParams = tObject({
expression: tString,
isFunction: tOptional(tBoolean),
exposeUtilityScript: tOptional(tBoolean),
arg: tType('SerializedArgument'),
});
scheme.FrameEvaluateExpressionResult = tObject({
@ -1620,6 +1637,8 @@ scheme.FrameSetInputFilesParams = tObject({
mimeType: tOptional(tString),
buffer: tBinary,
}))),
localDirectory: tOptional(tString),
directoryStream: tOptional(tChannel(['WritableStream'])),
localPaths: tOptional(tArray(tString)),
streams: tOptional(tArray(tChannel(['WritableStream']))),
timeout: tOptional(tNumber),
@ -1987,6 +2006,8 @@ scheme.ElementHandleSetInputFilesParams = tObject({
mimeType: tOptional(tString),
buffer: tBinary,
}))),
localDirectory: tOptional(tString),
directoryStream: tOptional(tChannel(['WritableStream'])),
localPaths: tOptional(tArray(tString)),
streams: tOptional(tArray(tChannel(['WritableStream']))),
timeout: tOptional(tNumber),

View file

@ -13,3 +13,4 @@
.externalNativeBuild
.cxx
local.properties
build/

View file

@ -1 +0,0 @@
/build

View file

@ -8,6 +8,7 @@ android {
defaultConfig {
applicationId "com.microsoft.playwright.androiddriver"
minSdkVersion 21
// noinspection ExpiredTargetSdkVersion
targetSdkVersion 29
versionCode 1
versionName "1.0"
@ -25,10 +26,11 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'com.microsoft.playwright.androiddriver'
}
dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.playwright.androiddriver">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"

View file

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.0"
classpath 'com.android.tools.build:gradle:8.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -17,3 +17,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -1,6 +1,5 @@
#Tue Dec 08 09:38:33 PST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

View file

@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -89,84 +140,101 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,38 +64,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View file

@ -24,6 +24,7 @@ import type { Download } from './download';
import type * as frames from './frames';
import { helper } from './helper';
import * as network from './network';
import { InitScript } from './page';
import type { PageDelegate } from './page';
import { Page, PageBinding } from './page';
import type { Progress, ProgressController } from './progress';
@ -84,7 +85,7 @@ export abstract class BrowserContext extends SdkObject {
private _customCloseHandler?: () => Promise<any>;
readonly _tempDirs: string[] = [];
private _settingStorageState = false;
readonly initScripts: string[] = [];
readonly initScripts: InitScript[] = [];
private _routesInFlight = new Set<network.Route>();
private _debugger!: Debugger;
_closeReason: string | undefined;
@ -216,6 +217,7 @@ export abstract class BrowserContext extends SdkObject {
await this._resetStorage();
await this._removeExposedBindings();
await this._removeInitScripts();
this.clock.markAsUninstalled();
// TODO: following can be optimized to not perform noops.
if (this._options.permissions)
await this.grantPermissions(this._options.permissions);
@ -265,7 +267,7 @@ export abstract class BrowserContext extends SdkObject {
protected abstract doGrantPermissions(origin: string, permissions: string[]): Promise<void>;
protected abstract doClearPermissions(): Promise<void>;
protected abstract doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
protected abstract doAddInitScript(expression: string): Promise<void>;
protected abstract doAddInitScript(initScript: InitScript): Promise<void>;
protected abstract doRemoveInitScripts(): Promise<void>;
protected abstract doExposeBinding(binding: PageBinding): Promise<void>;
protected abstract doRemoveExposedBindings(): Promise<void>;
@ -402,9 +404,10 @@ export abstract class BrowserContext extends SdkObject {
this._options.httpCredentials = { username, password: password || '' };
}
async addInitScript(script: string) {
this.initScripts.push(script);
await this.doAddInitScript(script);
async addInitScript(source: string) {
const initScript = new InitScript(source);
this.initScripts.push(initScript);
await this.doAddInitScript(initScript);
}
async _removeInitScripts(): Promise<void> {
@ -653,8 +656,13 @@ export function validateBrowserContextOptions(options: channels.BrowserNewContex
throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
if (options.noDefaultViewport && !!options.isMobile)
throw new Error(`"isMobile" option is not supported with null "viewport"`);
if (options.acceptDownloads === undefined)
if (options.acceptDownloads === undefined && browserOptions.name !== 'electron')
options.acceptDownloads = 'accept';
// Electron requires explicit acceptDownloads: true since we wait for
// https://github.com/electron/electron/pull/41718 to be widely shipped.
// In 6-12 months, we can remove this check.
else if (options.acceptDownloads === undefined && browserOptions.name === 'electron')
options.acceptDownloads = 'internal-browser-default';
if (!options.viewport && !options.noDefaultViewport)
options.viewport = { width: 1280, height: 720 };
if (options.recordVideo) {

View file

@ -305,9 +305,12 @@ export class Chromium extends BrowserType {
if (os.platform() === 'darwin') {
// See https://github.com/microsoft/playwright/issues/7362
chromeArguments.push('--enable-use-zoom-for-dsf=false');
}
if (options.headless) {
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025.
if (options.headless)
chromeArguments.push('--use-angle');
// See also https://github.com/microsoft/playwright/issues/30585
// and chromium fix at https://issues.chromium.org/issues/338414704.
chromeArguments.push('--enable-gpu');
}
if (options.devtools)
@ -316,7 +319,7 @@ export class Chromium extends BrowserType {
if (process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW)
chromeArguments.push('--headless=new');
else
chromeArguments.push('--headless');
chromeArguments.push('--headless=old');
chromeArguments.push(
'--hide-scrollbars',

Some files were not shown because too many files have changed in this diff Show more