Merge branch 'main' into sharding-algorithm
This commit is contained in:
commit
843d62917f
23
.github/actions/run-test/action.yml
vendored
23
.github/actions/run-test/action.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/create_test_report.yml
vendored
2
.github/workflows/create_test_report.yml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
42
.github/workflows/tests_electron.yml
vendored
42
.github/workflows/tests_electron.yml
vendored
|
|
@ -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
161
.github/workflows/tests_others.yml
vendored
Normal 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:
|
||||
8
.github/workflows/tests_secondary.yml
vendored
8
.github/workflows/tests_secondary.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
56
.github/workflows/tests_stress.yml
vendored
56
.github/workflows/tests_stress.yml
vendored
|
|
@ -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() }}
|
||||
60
.github/workflows/tests_webview2.yml
vendored
60
.github/workflows/tests_webview2.yml
vendored
|
|
@ -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
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# 🎭 Playwright
|
||||
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](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.17<!-- 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/mozilla/gecko-dev"
|
||||
BASE_BRANCH="release"
|
||||
BASE_REVISION="f8704c84a751716bad093b9bdc482db53fe5b3ea"
|
||||
BASE_REVISION="bd7e0ac24a6fb1cddde3e45ea191b7abcc90cf56"
|
||||
|
|
|
|||
|
|
@ -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*/);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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,41 +163,47 @@ 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
|
||||
{
|
||||
// ...
|
||||
// ...
|
||||
[TestInitialize]
|
||||
public async Task SetUpAPITesting()
|
||||
{
|
||||
await CreateAPIRequestContext();
|
||||
await CreateTestRepository();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpAPITesting()
|
||||
{
|
||||
await CreateAPIRequestContext();
|
||||
await CreateTestRepository();
|
||||
}
|
||||
private async Task CreateTestRepository()
|
||||
{
|
||||
var resp = await Request.PostAsync("/user/repos", new()
|
||||
{
|
||||
DataObject = new Dictionary<string, string>()
|
||||
{
|
||||
["name"] = REPO,
|
||||
},
|
||||
});
|
||||
await Expect(resp).ToBeOKAsync();
|
||||
}
|
||||
|
||||
private async Task CreateTestRepository()
|
||||
{
|
||||
var resp = await Request.PostAsync("/user/repos", new()
|
||||
{
|
||||
DataObject = new Dictionary<string, string>()
|
||||
{
|
||||
["name"] = REPO,
|
||||
},
|
||||
});
|
||||
Assert.True(resp.Ok);
|
||||
}
|
||||
[TestCleanup]
|
||||
public async Task TearDownAPITesting()
|
||||
{
|
||||
await DeleteTestRepository();
|
||||
await Request.DisposeAsync();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task TearDownAPITesting()
|
||||
{
|
||||
await DeleteTestRepository();
|
||||
await Request.DisposeAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteTestRepository()
|
||||
{
|
||||
var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO);
|
||||
Assert.True(resp.Ok);
|
||||
}
|
||||
private async Task DeleteTestRepository()
|
||||
{
|
||||
var resp = await Request.DeleteAsync("/repos/" + USER + "/" + REPO);
|
||||
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>();
|
||||
// We set this header per GitHub guidelines.
|
||||
headers.Add("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);
|
||||
var headers = new Dictionary<string, string>
|
||||
{
|
||||
// We set this header per GitHub guidelines.
|
||||
{ "Accept", "application/vnd.github.v3+json" },
|
||||
// Add authorization token to all requests.
|
||||
// Assuming personal access token available in the environment.
|
||||
{ "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,21 +347,23 @@ project to check that it appears at the top of the list. The check is performed
|
|||
```csharp
|
||||
class TestGitHubAPI : PageTest
|
||||
{
|
||||
[Test]
|
||||
public async Task LastCreatedIssueShouldBeFirstInTheList()
|
||||
{
|
||||
var data = new Dictionary<string, string>();
|
||||
data.Add("title", "[Feature] request 1");
|
||||
data.Add("body", "Feature description");
|
||||
var newIssue = await Request.PostAsync("/repos/" + USER + "/" + REPO + "/issues", new() { DataObject = data });
|
||||
Assert.True(newIssue.Ok);
|
||||
[TestMethod]
|
||||
public async Task LastCreatedIssueShouldBeFirstInTheList()
|
||||
{
|
||||
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 });
|
||||
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.
|
||||
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");
|
||||
var firstIssue = Page.Locator("a[data-hovercard-type='issue']").First;
|
||||
await Expect(firstIssue).ToHaveTextAsync("[Feature] request 1");
|
||||
}
|
||||
// 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.
|
||||
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");
|
||||
var firstIssue = Page.Locator("a[data-hovercard-type='issue']").First;
|
||||
await Expect(firstIssue).ToHaveTextAsync("[Feature] request 1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -368,22 +373,23 @@ 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]
|
||||
public async Task LastCreatedIssueShouldBeOnTheServer()
|
||||
{
|
||||
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");
|
||||
await Page.Locator("text=New Issue").ClickAsync();
|
||||
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('/'));
|
||||
[TestMethod]
|
||||
public async Task LastCreatedIssueShouldBeOnTheServer()
|
||||
{
|
||||
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");
|
||||
await Page.Locator("text=New Issue").ClickAsync();
|
||||
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();
|
||||
var issueId = Page.Url.Substring(Page.Url.LastIndexOf('/'));
|
||||
|
||||
var newIssue = await Request.GetAsync("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId);
|
||||
Assert.True(newIssue.Ok);
|
||||
StringAssert.Contains(await newIssue.TextAsync(), "Bug report 1");
|
||||
}
|
||||
var newIssue = await Request.GetAsync("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId);
|
||||
await Expect(newIssue).ToBeOKAsync();
|
||||
StringAssert.Contains(await newIssue.TextAsync(), "Bug report 1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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]>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1044,21 +1044,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
|
||||
|
|
|
|||
|
|
@ -1,86 +1,241 @@
|
|||
# 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
|
||||
page.clock().pauseAt(Instant.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(Instant.now());
|
||||
page.clock().setFixedTime(Instant.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(Instant.now());
|
||||
page.clock().setSystemTime(Instant.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]>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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[] {});
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -3927,6 +3927,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.
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]>
|
||||
|
|
|
|||
|
|
@ -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]>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ Google Chrome and Microsoft Edge respect enterprise policies, which include limi
|
|||
|
||||
### Firefox
|
||||
|
||||
Playwright's Firefox version matches the recent [Firefox Stable](https://www.mozilla.org/en-US/firefox/new/) build. Playwright doesn't work with the branded version of Firefox since it relies on patches.
|
||||
Playwright's Firefox version matches the recent [Firefox Stable](https://www.mozilla.org/en-US/firefox/new/) build. Playwright doesn't work with the branded version of Firefox since it relies on patches.
|
||||
|
||||
### WebKit
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
353
docs/src/clock.md
Normal file
353
docs/src/clock.md
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
---
|
||||
id: clock
|
||||
title: "Clock"
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
[`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.
|
||||
page.clock().install(new Clock.InstallOptions().setTime(Instant.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(Instant.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
|
||||
// Initialize clock with a specific time, let the page load naturally.
|
||||
page.clock().install(new Clock.InstallOptions()
|
||||
.setTime(Instant.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(Instant.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");
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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[] {});
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -637,7 +637,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
|
||||
|
|
|
|||
|
|
@ -6,6 +6,98 @@ toc_max_heading_level: 2
|
|||
|
||||
import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||
|
||||
## 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.
|
||||
|
||||
```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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -501,27 +501,6 @@ Defines the algorithm to be used for sharding. Defaults to `'partition'`.
|
|||
`'round-robin'`.
|
||||
|
||||
|
||||
## 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]>
|
||||
|
|
|
|||
|
|
@ -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].
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 user’s perspective, it likely means the automated test has uncovered a genuine bug in your code.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
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') });
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
})()`);
|
||||
```
|
||||
|
|
|
|||
|
|
@ -13,20 +13,25 @@ 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')` |
|
||||
| `screen.findByText('...')` | `component.getByText('...')` |
|
||||
| `screen.getByTestId('...')` | `component.getByTestId('...')` |
|
||||
| `screen.queryByPlaceholderText('...')` | `component.getByPlaceholder('...')` |
|
||||
| `screen.getByRole('button', { pressed: true })` | `component.getByRole('button', { pressed: true })`|
|
||||
| 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.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('...')` |
|
||||
| `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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
|
|
@ -86,6 +86,10 @@ See console logs from the browser as well as from your test. Different icons are
|
|||
|
||||

|
||||
|
||||
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
|
|||
|
||||

|
||||
|
||||
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">
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
632
package-lock.json
generated
632
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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://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
|
||||
|
|
@ -3,31 +3,31 @@
|
|||
"browsers": [
|
||||
{
|
||||
"name": "chromium",
|
||||
"revision": "1121",
|
||||
"revision": "1124",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "126.0.6478.26"
|
||||
"browserVersion": "127.0.6533.17"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
"revision": "1227",
|
||||
"revision": "1231",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "127.0.6510.0"
|
||||
"browserVersion": "128.0.6536.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": "2037",
|
||||
"installByDefault": true,
|
||||
"revisionOverrides": {
|
||||
"mac10.14": "1446",
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
},
|
||||
{
|
||||
"name": "android",
|
||||
"revision": "1000",
|
||||
"revision": "1001",
|
||||
"installByDefault": false
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -85,6 +85,16 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
|||
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||
const context = BrowserContext.from(response.context);
|
||||
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
||||
if (!forReuse && process.env.PW_CLOCK === 'frozen') {
|
||||
await this._wrapApiCall(async () => {
|
||||
await context.clock.install({ time: 0 });
|
||||
await context.clock.pauseAt(1000);
|
||||
}, true);
|
||||
} else if (!forReuse && process.env.PW_CLOCK === 'realtime') {
|
||||
await this._wrapApiCall(async () => {
|
||||
await context.clock.install({ time: 0 });
|
||||
}, true);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
directoryStream: rootDir,
|
||||
streams: localDirectory ? undefined : writableStreams,
|
||||
};
|
||||
}
|
||||
return { localPaths: items.map(f => path.resolve(f as string)) as string[] };
|
||||
return {
|
||||
localPaths,
|
||||
localDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
const payloads = items as FilePayload[];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
name: tString,
|
||||
lastModifiedMs: tOptional(tNumber),
|
||||
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({
|
||||
|
|
@ -1425,7 +1434,6 @@ scheme.FrameDispatchEventResult = tOptional(tObject({}));
|
|||
scheme.FrameEvaluateExpressionParams = tObject({
|
||||
expression: tString,
|
||||
isFunction: tOptional(tBoolean),
|
||||
exposeUtilityScript: tOptional(tBoolean),
|
||||
arg: tType('SerializedArgument'),
|
||||
});
|
||||
scheme.FrameEvaluateExpressionResult = tObject({
|
||||
|
|
@ -1620,6 +1628,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 +1997,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),
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@
|
|||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
build/
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
/build
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -9,4 +8,4 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true" />
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -21,4 +21,4 @@ allprojects {
|
|||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
android.enableJetifier=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" "$@"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,20 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@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
|
||||
@rem Gradle startup script for Windows
|
||||
|
|
@ -9,19 +25,22 @@
|
|||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -216,6 +216,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);
|
||||
|
|
@ -653,8 +654,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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ export class CRBrowserContext extends BrowserContext {
|
|||
override async _initialize() {
|
||||
assert(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this));
|
||||
const promises: Promise<any>[] = [super._initialize()];
|
||||
if (this._browser.options.name !== 'electron' && this._browser.options.name !== 'clank' && this._options.acceptDownloads !== 'internal-browser-default') {
|
||||
if (this._browser.options.name !== 'clank' && this._options.acceptDownloads !== 'internal-browser-default') {
|
||||
promises.push(this._browser._session.send('Browser.setDownloadBehavior', {
|
||||
behavior: this._options.acceptDownloads === 'accept' ? 'allowAndName' : 'deny',
|
||||
browserContextId: this._browserContextId,
|
||||
|
|
@ -433,6 +433,7 @@ export class CRBrowserContext extends BrowserContext {
|
|||
['payment-handler', 'paymentHandler'],
|
||||
// chrome-specific permissions we have.
|
||||
['midi-sysex', 'midiSysex'],
|
||||
['storage-access', 'storageAccess'],
|
||||
]);
|
||||
const filtered = permissions.map(permission => {
|
||||
const protocolPermission = webPermissionToProtocol.get(permission);
|
||||
|
|
|
|||
|
|
@ -332,12 +332,13 @@ export class CRNetworkManager {
|
|||
}
|
||||
|
||||
let route = null;
|
||||
let headersOverride: types.HeadersArray | undefined;
|
||||
if (requestPausedEvent) {
|
||||
// We do not support intercepting redirects.
|
||||
if (redirectedFrom || (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled)) {
|
||||
// Chromium does not preserve header overrides between redirects, so we have to do it ourselves.
|
||||
const headers = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
|
||||
requestPausedSessionInfo!.session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers });
|
||||
headersOverride = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
|
||||
requestPausedSessionInfo!.session._sendMayFail('Fetch.continueRequest', { requestId: requestPausedEvent.requestId, headers: headersOverride });
|
||||
} else {
|
||||
route = new RouteImpl(requestPausedSessionInfo!.session, requestPausedEvent.requestId);
|
||||
}
|
||||
|
|
@ -353,7 +354,8 @@ export class CRNetworkManager {
|
|||
route,
|
||||
requestWillBeSentEvent,
|
||||
requestPausedEvent,
|
||||
redirectedFrom
|
||||
redirectedFrom,
|
||||
headersOverride: headersOverride || null,
|
||||
});
|
||||
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
||||
|
||||
|
|
@ -361,7 +363,7 @@ export class CRNetworkManager {
|
|||
// We will not receive extra info when intercepting the request.
|
||||
// Use the headers from the Fetch.requestPausedPayload and release the allHeaders()
|
||||
// right away, so that client can call it from the route handler.
|
||||
request.request.setRawRequestHeaders(headersObjectToArray(requestPausedEvent.request.headers, '\n'));
|
||||
request.request.setRawRequestHeaders(headersOverride ?? headersObjectToArray(requestPausedEvent.request.headers, '\n'));
|
||||
}
|
||||
(this._page?._frameManager || this._serviceWorker)!.requestStarted(request.request, route || undefined);
|
||||
}
|
||||
|
|
@ -568,8 +570,9 @@ class InterceptableRequest {
|
|||
requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload;
|
||||
requestPausedEvent: Protocol.Fetch.requestPausedPayload | undefined;
|
||||
redirectedFrom: InterceptableRequest | null;
|
||||
headersOverride: types.HeadersArray | null;
|
||||
}) {
|
||||
const { session, context, frame, documentId, route, requestWillBeSentEvent, requestPausedEvent, redirectedFrom, serviceWorker } = options;
|
||||
const { session, context, frame, documentId, route, requestWillBeSentEvent, requestPausedEvent, redirectedFrom, serviceWorker, headersOverride } = options;
|
||||
this.session = session;
|
||||
this._timestamp = requestWillBeSentEvent.timestamp;
|
||||
this._wallTime = requestWillBeSentEvent.wallTime;
|
||||
|
|
@ -591,7 +594,7 @@ class InterceptableRequest {
|
|||
if (entries && entries.length)
|
||||
postDataBuffer = Buffer.concat(entries.map(entry => Buffer.from(entry.bytes!, 'base64')));
|
||||
|
||||
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
||||
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersOverride || headersObjectToArray(headers));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -510,28 +510,30 @@ class FrameSession {
|
|||
this._handleFrameTree(frameTree);
|
||||
this._addRendererListeners();
|
||||
}
|
||||
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)!];
|
||||
for (const frame of localFrames) {
|
||||
// Note: frames might be removed before we send these.
|
||||
this._client._sendMayFail('Page.createIsolatedWorld', {
|
||||
frameId: frame._id,
|
||||
grantUniveralAccess: true,
|
||||
worldName: UTILITY_WORLD_NAME,
|
||||
});
|
||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
||||
frame.evaluateExpression(binding.source).catch(e => {});
|
||||
for (const source of this._crPage._browserContext.initScripts)
|
||||
frame.evaluateExpression(source).catch(e => {});
|
||||
}
|
||||
|
||||
const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
|
||||
if (isInitialEmptyPage) {
|
||||
// Ignore lifecycle events for the initial empty page. It is never the final page
|
||||
// Ignore lifecycle events, worlds and bindings for the initial empty page. It is never the final page
|
||||
// hence we are going to get more lifecycle updates after the actual navigation has
|
||||
// started (even if the target url is about:blank).
|
||||
lifecycleEventsEnabled.catch(e => {}).then(() => {
|
||||
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
|
||||
});
|
||||
} else {
|
||||
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)!];
|
||||
for (const frame of localFrames) {
|
||||
// Note: frames might be removed before we send these.
|
||||
this._client._sendMayFail('Page.createIsolatedWorld', {
|
||||
frameId: frame._id,
|
||||
grantUniveralAccess: true,
|
||||
worldName: UTILITY_WORLD_NAME,
|
||||
});
|
||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
||||
frame.evaluateExpression(binding.source).catch(e => {});
|
||||
for (const source of this._crPage._browserContext.initScripts)
|
||||
frame.evaluateExpression(source).catch(e => {});
|
||||
}
|
||||
|
||||
this._firstNonInitialNavigationCommittedFulfill();
|
||||
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -758,7 +758,7 @@ Does not always exist (e.g. for unsafe form submission urls).
|
|||
* Enum indicating the reason a response has been blocked. These reasons are
|
||||
refinements of the net error BLOCKED_BY_RESPONSE.
|
||||
*/
|
||||
export type BlockedByResponseReason = "CoepFrameResourceNeedsCoepHeader"|"CoopSandboxedIFrameCannotNavigateToCoopPage"|"CorpNotSameOrigin"|"CorpNotSameOriginAfterDefaultedToSameOriginByCoep"|"CorpNotSameSite";
|
||||
export type BlockedByResponseReason = "CoepFrameResourceNeedsCoepHeader"|"CoopSandboxedIFrameCannotNavigateToCoopPage"|"CorpNotSameOrigin"|"CorpNotSameOriginAfterDefaultedToSameOriginByCoep"|"CorpNotSameOriginAfterDefaultedToSameOriginByDip"|"CorpNotSameOriginAfterDefaultedToSameOriginByCoepAndDip"|"CorpNotSameSite";
|
||||
/**
|
||||
* Details for a request that has been blocked with the BLOCKED_BY_RESPONSE
|
||||
code. Currently only used for COEP/COOP, but may be extended to include
|
||||
|
|
@ -934,7 +934,7 @@ Should be updated alongside RequestIdTokenStatus in
|
|||
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
|
||||
all cases except for success.
|
||||
*/
|
||||
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByButtonMode";
|
||||
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByButtonMode"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching";
|
||||
export interface FederatedAuthUserInfoRequestIssueDetails {
|
||||
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
|
||||
}
|
||||
|
|
@ -3486,7 +3486,7 @@ front-end.
|
|||
/**
|
||||
* Pseudo element type.
|
||||
*/
|
||||
export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-markers"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new";
|
||||
export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-marker-group"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new";
|
||||
/**
|
||||
* Shadow root type.
|
||||
*/
|
||||
|
|
@ -4801,6 +4801,29 @@ container queries against this container.
|
|||
*/
|
||||
nodeIds: NodeId[];
|
||||
}
|
||||
/**
|
||||
* Returns the target anchor element of the given anchor query according to
|
||||
https://www.w3.org/TR/css-anchor-position-1/#target.
|
||||
*/
|
||||
export type getAnchorElementParameters = {
|
||||
/**
|
||||
* Id of the positioned element from which to find the anchor.
|
||||
*/
|
||||
nodeId: NodeId;
|
||||
/**
|
||||
* An optional anchor specifier, as defined in
|
||||
https://www.w3.org/TR/css-anchor-position-1/#anchor-specifier.
|
||||
If not provided, it will return the implicit anchor element for
|
||||
the given positioned element.
|
||||
*/
|
||||
anchorSpecifier?: string;
|
||||
}
|
||||
export type getAnchorElementReturnValue = {
|
||||
/**
|
||||
* The anchor element of the given anchor query.
|
||||
*/
|
||||
nodeId: NodeId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -8116,6 +8139,14 @@ milliseconds relatively to this requestTime.
|
|||
* Settled fetch event respondWith promise.
|
||||
*/
|
||||
workerRespondWithSettled: number;
|
||||
/**
|
||||
* Started ServiceWorker static routing source evaluation.
|
||||
*/
|
||||
workerRouterEvaluationStart?: number;
|
||||
/**
|
||||
* Started cache lookup when the source was evaluated to `cache`.
|
||||
*/
|
||||
workerCacheLookupStart?: number;
|
||||
/**
|
||||
* Started sending request.
|
||||
*/
|
||||
|
|
@ -8323,7 +8354,7 @@ applicable or not known.
|
|||
/**
|
||||
* The reason why request was blocked.
|
||||
*/
|
||||
export type BlockedReason = "other"|"csp"|"mixed-content"|"origin"|"inspector"|"subresource-filter"|"content-type"|"coep-frame-resource-needs-coep-header"|"coop-sandboxed-iframe-cannot-navigate-to-coop-page"|"corp-not-same-origin"|"corp-not-same-origin-after-defaulted-to-same-origin-by-coep"|"corp-not-same-site";
|
||||
export type BlockedReason = "other"|"csp"|"mixed-content"|"origin"|"inspector"|"subresource-filter"|"content-type"|"coep-frame-resource-needs-coep-header"|"coop-sandboxed-iframe-cannot-navigate-to-coop-page"|"corp-not-same-origin"|"corp-not-same-origin-after-defaulted-to-same-origin-by-coep"|"corp-not-same-origin-after-defaulted-to-same-origin-by-dip"|"corp-not-same-origin-after-defaulted-to-same-origin-by-coep-and-dip"|"corp-not-same-site";
|
||||
/**
|
||||
* The reason why request was blocked.
|
||||
*/
|
||||
|
|
@ -8374,6 +8405,10 @@ be set, otherwiser no value will be set.
|
|||
field will be set, otherwise no value will be set.
|
||||
*/
|
||||
matchedSourceType?: ServiceWorkerRouterSource;
|
||||
/**
|
||||
* The actual router source used.
|
||||
*/
|
||||
actualSourceType?: ServiceWorkerRouterSource;
|
||||
}
|
||||
/**
|
||||
* HTTP response data.
|
||||
|
|
@ -8600,6 +8635,21 @@ module) (0-based).
|
|||
*/
|
||||
requestId?: RequestId;
|
||||
}
|
||||
/**
|
||||
* cookiePartitionKey object
|
||||
The representation of the components of the key that are created by the cookiePartitionKey class contained in net/cookies/cookie_partition_key.h.
|
||||
*/
|
||||
export interface CookiePartitionKey {
|
||||
/**
|
||||
* The site of the top-level URL the browser was visiting at the start
|
||||
of the request to the endpoint that set the cookie.
|
||||
*/
|
||||
topLevelSite: string;
|
||||
/**
|
||||
* Indicates if the cookie has any ancestors that are cross-site to the topLevelSite.
|
||||
*/
|
||||
hasCrossSiteAncestor: boolean;
|
||||
}
|
||||
/**
|
||||
* Cookie object
|
||||
*/
|
||||
|
|
@ -8663,10 +8713,9 @@ This is a temporary ability and it will be removed in the future.
|
|||
*/
|
||||
sourcePort: number;
|
||||
/**
|
||||
* Cookie partition key. The site of the top-level URL the browser was visiting at the start
|
||||
of the request to the endpoint that set the cookie.
|
||||
* Cookie partition key.
|
||||
*/
|
||||
partitionKey?: string;
|
||||
partitionKey?: CookiePartitionKey;
|
||||
/**
|
||||
* True if cookie partition key is opaque.
|
||||
*/
|
||||
|
|
@ -8683,7 +8732,7 @@ of the request to the endpoint that set the cookie.
|
|||
/**
|
||||
* Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request.
|
||||
*/
|
||||
export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"CorsOptIn";
|
||||
export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"CorsOptIn"|"Scheme";
|
||||
/**
|
||||
* A cookie which was not stored from a response with the corresponding reason.
|
||||
*/
|
||||
|
|
@ -8801,11 +8850,9 @@ This is a temporary ability and it will be removed in the future.
|
|||
*/
|
||||
sourcePort?: number;
|
||||
/**
|
||||
* Cookie partition key. The site of the top-level URL the browser was visiting at the start
|
||||
of the request to the endpoint that set the cookie.
|
||||
If not set, the cookie will be set as not partitioned.
|
||||
* Cookie partition key. If not set, the cookie will be set as not partitioned.
|
||||
*/
|
||||
partitionKey?: string;
|
||||
partitionKey?: CookiePartitionKey;
|
||||
}
|
||||
/**
|
||||
* Authorization challenge for HTTP status code 401 or 407.
|
||||
|
|
@ -9629,7 +9676,7 @@ available, such as in the case of HTTP/2 or QUIC.
|
|||
* The cookie partition key that will be used to store partitioned cookies set in this response.
|
||||
Only sent when partitioned cookies are enabled.
|
||||
*/
|
||||
cookiePartitionKey?: string;
|
||||
cookiePartitionKey?: CookiePartitionKey;
|
||||
/**
|
||||
* True if partitioned cookies are enabled, but the partition key is not serializable to string.
|
||||
*/
|
||||
|
|
@ -9668,7 +9715,7 @@ or after the response was received.
|
|||
of the operation already exists und thus, the operation was abort
|
||||
preemptively (e.g. a cache hit).
|
||||
*/
|
||||
status: "Ok"|"InvalidArgument"|"MissingIssuerKeys"|"FailedPrecondition"|"ResourceExhausted"|"AlreadyExists"|"Unavailable"|"Unauthorized"|"BadResponse"|"InternalError"|"UnknownError"|"FulfilledLocally";
|
||||
status: "Ok"|"InvalidArgument"|"MissingIssuerKeys"|"FailedPrecondition"|"ResourceExhausted"|"AlreadyExists"|"ResourceLimited"|"Unauthorized"|"BadResponse"|"InternalError"|"UnknownError"|"FulfilledLocally";
|
||||
type: TrustTokenOperationType;
|
||||
requestId: RequestId;
|
||||
/**
|
||||
|
|
@ -9905,10 +9952,10 @@ provided URL.
|
|||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* If specified, deletes only cookies with the the given name and partitionKey where domain
|
||||
matches provided URL.
|
||||
* If specified, deletes only cookies with the the given name and partitionKey where
|
||||
all partition key attributes match the cookie partition key attribute.
|
||||
*/
|
||||
partitionKey?: string;
|
||||
partitionKey?: CookiePartitionKey;
|
||||
}
|
||||
export type deleteCookiesReturnValue = {
|
||||
}
|
||||
|
|
@ -10218,11 +10265,9 @@ This is a temporary ability and it will be removed in the future.
|
|||
*/
|
||||
sourcePort?: number;
|
||||
/**
|
||||
* Cookie partition key. The site of the top-level URL the browser was visiting at the start
|
||||
of the request to the endpoint that set the cookie.
|
||||
If not set, the cookie will be set as not partitioned.
|
||||
* Cookie partition key. If not set, the cookie will be set as not partitioned.
|
||||
*/
|
||||
partitionKey?: string;
|
||||
partitionKey?: CookiePartitionKey;
|
||||
}
|
||||
export type setCookieReturnValue = {
|
||||
/**
|
||||
|
|
@ -11233,7 +11278,7 @@ as an ad.
|
|||
* All Permissions Policy features. This enum should match the one defined
|
||||
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
|
||||
*/
|
||||
export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
|
||||
export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
|
||||
/**
|
||||
* Reason for a permissions policy feature to be disabled.
|
||||
*/
|
||||
|
|
@ -11821,7 +11866,7 @@ https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-expl
|
|||
/**
|
||||
* List of not restored reasons for back-forward cache.
|
||||
*/
|
||||
export type BackForwardCacheNotRestoredReason = "NotPrimaryMainFrame"|"BackForwardCacheDisabled"|"RelatedActiveContentsExist"|"HTTPStatusNotOK"|"SchemeNotHTTPOrHTTPS"|"Loading"|"WasGrantedMediaAccess"|"DisableForRenderFrameHostCalled"|"DomainNotAllowed"|"HTTPMethodNotGET"|"SubframeIsNavigating"|"Timeout"|"CacheLimit"|"JavaScriptExecution"|"RendererProcessKilled"|"RendererProcessCrashed"|"SchedulerTrackedFeatureUsed"|"ConflictingBrowsingInstance"|"CacheFlushed"|"ServiceWorkerVersionActivation"|"SessionRestored"|"ServiceWorkerPostMessage"|"EnteredBackForwardCacheBeforeServiceWorkerHostAdded"|"RenderFrameHostReused_SameSite"|"RenderFrameHostReused_CrossSite"|"ServiceWorkerClaim"|"IgnoreEventAndEvict"|"HaveInnerContents"|"TimeoutPuttingInCache"|"BackForwardCacheDisabledByLowMemory"|"BackForwardCacheDisabledByCommandLine"|"NetworkRequestDatapipeDrainedAsBytesConsumer"|"NetworkRequestRedirected"|"NetworkRequestTimeout"|"NetworkExceedsBufferLimit"|"NavigationCancelledWhileRestoring"|"NotMostRecentNavigationEntry"|"BackForwardCacheDisabledForPrerender"|"UserAgentOverrideDiffers"|"ForegroundCacheLimit"|"BrowsingInstanceNotSwapped"|"BackForwardCacheDisabledForDelegate"|"UnloadHandlerExistsInMainFrame"|"UnloadHandlerExistsInSubFrame"|"ServiceWorkerUnregistration"|"CacheControlNoStore"|"CacheControlNoStoreCookieModified"|"CacheControlNoStoreHTTPOnlyCookieModified"|"NoResponseHead"|"Unknown"|"ActivationNavigationsDisallowedForBug1234857"|"ErrorDocument"|"FencedFramesEmbedder"|"CookieDisabled"|"HTTPAuthRequired"|"CookieFlushed"|"BroadcastChannelOnMessage"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"OutstandingNetworkRequestOthers"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"KeepaliveRequest"|"IndexedDBEvent"|"Dummy"|"JsNetworkRequestReceivedCacheControlNoStoreResource"|"WebRTCSticky"|"WebTransportSticky"|"WebSocketSticky"|"SmartCard"|"LiveMediaStreamTrack"|"UnloadHandler"|"ParserAborted"|"ContentSecurityHandler"|"ContentWebAuthenticationAPI"|"ContentFileChooser"|"ContentSerial"|"ContentFileSystemAccess"|"ContentMediaDevicesDispatcherHost"|"ContentWebBluetooth"|"ContentWebUSB"|"ContentMediaSessionService"|"ContentScreenReader"|"EmbedderPopupBlockerTabHelper"|"EmbedderSafeBrowsingTriggeredPopupBlocker"|"EmbedderSafeBrowsingThreatDetails"|"EmbedderAppBannerManager"|"EmbedderDomDistillerViewerSource"|"EmbedderDomDistillerSelfDeletingRequestDelegate"|"EmbedderOomInterventionTabHelper"|"EmbedderOfflinePage"|"EmbedderChromePasswordManagerClientBindCredentialManager"|"EmbedderPermissionRequestManager"|"EmbedderModalDialog"|"EmbedderExtensions"|"EmbedderExtensionMessaging"|"EmbedderExtensionMessagingForOpenPort"|"EmbedderExtensionSentMessageToCachedFrame"|"RequestedByWebViewClient";
|
||||
export type BackForwardCacheNotRestoredReason = "NotPrimaryMainFrame"|"BackForwardCacheDisabled"|"RelatedActiveContentsExist"|"HTTPStatusNotOK"|"SchemeNotHTTPOrHTTPS"|"Loading"|"WasGrantedMediaAccess"|"DisableForRenderFrameHostCalled"|"DomainNotAllowed"|"HTTPMethodNotGET"|"SubframeIsNavigating"|"Timeout"|"CacheLimit"|"JavaScriptExecution"|"RendererProcessKilled"|"RendererProcessCrashed"|"SchedulerTrackedFeatureUsed"|"ConflictingBrowsingInstance"|"CacheFlushed"|"ServiceWorkerVersionActivation"|"SessionRestored"|"ServiceWorkerPostMessage"|"EnteredBackForwardCacheBeforeServiceWorkerHostAdded"|"RenderFrameHostReused_SameSite"|"RenderFrameHostReused_CrossSite"|"ServiceWorkerClaim"|"IgnoreEventAndEvict"|"HaveInnerContents"|"TimeoutPuttingInCache"|"BackForwardCacheDisabledByLowMemory"|"BackForwardCacheDisabledByCommandLine"|"NetworkRequestDatapipeDrainedAsBytesConsumer"|"NetworkRequestRedirected"|"NetworkRequestTimeout"|"NetworkExceedsBufferLimit"|"NavigationCancelledWhileRestoring"|"NotMostRecentNavigationEntry"|"BackForwardCacheDisabledForPrerender"|"UserAgentOverrideDiffers"|"ForegroundCacheLimit"|"BrowsingInstanceNotSwapped"|"BackForwardCacheDisabledForDelegate"|"UnloadHandlerExistsInMainFrame"|"UnloadHandlerExistsInSubFrame"|"ServiceWorkerUnregistration"|"CacheControlNoStore"|"CacheControlNoStoreCookieModified"|"CacheControlNoStoreHTTPOnlyCookieModified"|"NoResponseHead"|"Unknown"|"ActivationNavigationsDisallowedForBug1234857"|"ErrorDocument"|"FencedFramesEmbedder"|"CookieDisabled"|"HTTPAuthRequired"|"CookieFlushed"|"BroadcastChannelOnMessage"|"WebViewSettingsChanged"|"WebViewJavaScriptObjectChanged"|"WebViewMessageListenerInjected"|"WebViewSafeBrowsingAllowlistChanged"|"WebViewDocumentStartJavascriptChanged"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"OutstandingNetworkRequestOthers"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"KeepaliveRequest"|"IndexedDBEvent"|"Dummy"|"JsNetworkRequestReceivedCacheControlNoStoreResource"|"WebRTCSticky"|"WebTransportSticky"|"WebSocketSticky"|"SmartCard"|"LiveMediaStreamTrack"|"UnloadHandler"|"ParserAborted"|"ContentSecurityHandler"|"ContentWebAuthenticationAPI"|"ContentFileChooser"|"ContentSerial"|"ContentFileSystemAccess"|"ContentMediaDevicesDispatcherHost"|"ContentWebBluetooth"|"ContentWebUSB"|"ContentMediaSessionService"|"ContentScreenReader"|"EmbedderPopupBlockerTabHelper"|"EmbedderSafeBrowsingTriggeredPopupBlocker"|"EmbedderSafeBrowsingThreatDetails"|"EmbedderAppBannerManager"|"EmbedderDomDistillerViewerSource"|"EmbedderDomDistillerSelfDeletingRequestDelegate"|"EmbedderOomInterventionTabHelper"|"EmbedderOfflinePage"|"EmbedderChromePasswordManagerClientBindCredentialManager"|"EmbedderPermissionRequestManager"|"EmbedderModalDialog"|"EmbedderExtensions"|"EmbedderExtensionMessaging"|"EmbedderExtensionMessagingForOpenPort"|"EmbedderExtensionSentMessageToCachedFrame"|"RequestedByWebViewClient";
|
||||
/**
|
||||
* Types of not restored reasons for back-forward cache.
|
||||
*/
|
||||
|
|
@ -12737,6 +12782,12 @@ in which case the content will be scaled to fit the paper size.
|
|||
Argument will be ignored if reloading dataURL origin.
|
||||
*/
|
||||
scriptToEvaluateOnLoad?: string;
|
||||
/**
|
||||
* If set, an error will be thrown if the target page's main frame's
|
||||
loader id does not match the provided id. This prevents accidentally
|
||||
reloading an unintended target in case there's a racing navigation.
|
||||
*/
|
||||
loaderId?: Network.LoaderId;
|
||||
}
|
||||
export type reloadReturnValue = {
|
||||
}
|
||||
|
|
@ -17019,9 +17070,8 @@ manifestId.
|
|||
}
|
||||
/**
|
||||
* Launches the installed web app, or an url in the same web app instead of the
|
||||
default start url if it is provided. Returns a tab / web contents based
|
||||
Target.TargetID which can be used to attach to via Target.attachToTarget or
|
||||
similar APIs.
|
||||
default start url if it is provided. Returns a page Target.TargetID which
|
||||
can be used to attach to via Target.attachToTarget or similar APIs.
|
||||
*/
|
||||
export type launchParameters = {
|
||||
manifestId: string;
|
||||
|
|
@ -17033,6 +17083,41 @@ similar APIs.
|
|||
*/
|
||||
targetId: Target.TargetID;
|
||||
}
|
||||
/**
|
||||
* Opens one or more local files from an installed web app identified by its
|
||||
manifestId. The web app needs to have file handlers registered to process
|
||||
the files. The API returns one or more page Target.TargetIDs which can be
|
||||
used to attach to via Target.attachToTarget or similar APIs.
|
||||
If some files in the parameters cannot be handled by the web app, they will
|
||||
be ignored. If none of the files can be handled, this API returns an error.
|
||||
If no files provided as the parameter, this API also returns an error.
|
||||
|
||||
According to the definition of the file handlers in the manifest file, one
|
||||
Target.TargetID may represent a page handling one or more files. The order
|
||||
of the returned Target.TargetIDs is not guaranteed.
|
||||
|
||||
TODO(crbug.com/339454034): Check the existences of the input files.
|
||||
*/
|
||||
export type launchFilesInAppParameters = {
|
||||
manifestId: string;
|
||||
files: string[];
|
||||
}
|
||||
export type launchFilesInAppReturnValue = {
|
||||
/**
|
||||
* IDs of the tab targets created as the result.
|
||||
*/
|
||||
targetIds: Target.TargetID[];
|
||||
}
|
||||
/**
|
||||
* Opens the current page in its web app identified by the manifest id, needs
|
||||
to be called on a page target. This function returns immediately without
|
||||
waiting for the app finishing loading.
|
||||
*/
|
||||
export type openCurrentPageInAppParameters = {
|
||||
manifestId: string;
|
||||
}
|
||||
export type openCurrentPageInAppReturnValue = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -20004,6 +20089,7 @@ Error was thrown.
|
|||
"DOM.getFrameOwner": DOM.getFrameOwnerParameters;
|
||||
"DOM.getContainerForNode": DOM.getContainerForNodeParameters;
|
||||
"DOM.getQueryingDescendantsForContainer": DOM.getQueryingDescendantsForContainerParameters;
|
||||
"DOM.getAnchorElement": DOM.getAnchorElementParameters;
|
||||
"DOMDebugger.getEventListeners": DOMDebugger.getEventListenersParameters;
|
||||
"DOMDebugger.removeDOMBreakpoint": DOMDebugger.removeDOMBreakpointParameters;
|
||||
"DOMDebugger.removeEventListenerBreakpoint": DOMDebugger.removeEventListenerBreakpointParameters;
|
||||
|
|
@ -20373,6 +20459,8 @@ Error was thrown.
|
|||
"PWA.install": PWA.installParameters;
|
||||
"PWA.uninstall": PWA.uninstallParameters;
|
||||
"PWA.launch": PWA.launchParameters;
|
||||
"PWA.launchFilesInApp": PWA.launchFilesInAppParameters;
|
||||
"PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters;
|
||||
"Console.clearMessages": Console.clearMessagesParameters;
|
||||
"Console.disable": Console.disableParameters;
|
||||
"Console.enable": Console.enableParameters;
|
||||
|
|
@ -20597,6 +20685,7 @@ Error was thrown.
|
|||
"DOM.getFrameOwner": DOM.getFrameOwnerReturnValue;
|
||||
"DOM.getContainerForNode": DOM.getContainerForNodeReturnValue;
|
||||
"DOM.getQueryingDescendantsForContainer": DOM.getQueryingDescendantsForContainerReturnValue;
|
||||
"DOM.getAnchorElement": DOM.getAnchorElementReturnValue;
|
||||
"DOMDebugger.getEventListeners": DOMDebugger.getEventListenersReturnValue;
|
||||
"DOMDebugger.removeDOMBreakpoint": DOMDebugger.removeDOMBreakpointReturnValue;
|
||||
"DOMDebugger.removeEventListenerBreakpoint": DOMDebugger.removeEventListenerBreakpointReturnValue;
|
||||
|
|
@ -20966,6 +21055,8 @@ Error was thrown.
|
|||
"PWA.install": PWA.installReturnValue;
|
||||
"PWA.uninstall": PWA.uninstallReturnValue;
|
||||
"PWA.launch": PWA.launchReturnValue;
|
||||
"PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue;
|
||||
"PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue;
|
||||
"Console.clearMessages": Console.clearMessagesReturnValue;
|
||||
"Console.disable": Console.disableReturnValue;
|
||||
"Console.enable": Console.enableReturnValue;
|
||||
|
|
|
|||
|
|
@ -14,53 +14,82 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import * as fakeTimersSource from '../generated/fakeTimersSource';
|
||||
import * as clockSource from '../generated/clockSource';
|
||||
import { isJavaScriptErrorInEvaluate } from './javascript';
|
||||
|
||||
export class Clock {
|
||||
private _browserContext: BrowserContext;
|
||||
private _installed = false;
|
||||
private _scriptInstalled = false;
|
||||
|
||||
constructor(browserContext: BrowserContext) {
|
||||
this._browserContext = browserContext;
|
||||
}
|
||||
|
||||
async install(params: channels.BrowserContextClockInstallOptions) {
|
||||
if (this._installed)
|
||||
throw new Error('Cannot install more than one clock per context');
|
||||
this._installed = true;
|
||||
markAsUninstalled() {
|
||||
this._scriptInstalled = false;
|
||||
}
|
||||
|
||||
async fastForward(ticks: number | string) {
|
||||
await this._installIfNeeded();
|
||||
const ticksMillis = parseTicks(ticks);
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('fastForward', ${Date.now()}, ${ticksMillis})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.fastForward(${ticksMillis})`);
|
||||
}
|
||||
|
||||
async install(time: number | string | undefined) {
|
||||
await this._installIfNeeded();
|
||||
const timeMillis = time !== undefined ? parseTime(time) : Date.now();
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('install', ${Date.now()}, ${timeMillis})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.install(${timeMillis})`);
|
||||
}
|
||||
|
||||
async pauseAt(ticks: number | string) {
|
||||
await this._installIfNeeded();
|
||||
const timeMillis = parseTime(ticks);
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('pauseAt', ${Date.now()}, ${timeMillis})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.pauseAt(${timeMillis})`);
|
||||
}
|
||||
|
||||
async resume() {
|
||||
await this._installIfNeeded();
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('resume', ${Date.now()})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.resume()`);
|
||||
}
|
||||
|
||||
async setFixedTime(time: string | number) {
|
||||
await this._installIfNeeded();
|
||||
const timeMillis = parseTime(time);
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('setFixedTime', ${Date.now()}, ${timeMillis})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.setFixedTime(${timeMillis})`);
|
||||
}
|
||||
|
||||
async setSystemTime(time: string | number) {
|
||||
await this._installIfNeeded();
|
||||
const timeMillis = parseTime(time);
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('setSystemTime', ${Date.now()}, ${timeMillis})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.setSystemTime(${timeMillis})`);
|
||||
}
|
||||
|
||||
async runFor(ticks: number | string) {
|
||||
await this._installIfNeeded();
|
||||
const ticksMillis = parseTicks(ticks);
|
||||
await this._browserContext.addInitScript(`globalThis.__pwClock.controller.log('runFor', ${Date.now()}, ${ticksMillis})`);
|
||||
await this._evaluateInFrames(`globalThis.__pwClock.controller.runFor(${ticksMillis})`);
|
||||
}
|
||||
|
||||
private async _installIfNeeded() {
|
||||
if (this._scriptInstalled)
|
||||
return;
|
||||
this._scriptInstalled = true;
|
||||
const script = `(() => {
|
||||
const module = {};
|
||||
${fakeTimersSource.source}
|
||||
globalThis.__pwFakeTimers = (module.exports.install())(${JSON.stringify(params)});
|
||||
${clockSource.source}
|
||||
globalThis.__pwClock = (module.exports.inject())(globalThis);
|
||||
})();`;
|
||||
await this._addAndEvaluate(script);
|
||||
}
|
||||
|
||||
async jump(time: number | string) {
|
||||
this._assertInstalled();
|
||||
await this._addAndEvaluate(`globalThis.__pwFakeTimers.jump(${JSON.stringify(time)}); 0`);
|
||||
}
|
||||
|
||||
async runAll(): Promise<number> {
|
||||
this._assertInstalled();
|
||||
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.runAll()`);
|
||||
return await this._evaluateInFrames(`globalThis.__pwFakeTimers.runAllAsync()`);
|
||||
}
|
||||
|
||||
async runToLast(): Promise<number> {
|
||||
this._assertInstalled();
|
||||
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.runToLast()`);
|
||||
return await this._evaluateInFrames(`globalThis.__pwFakeTimers.runToLastAsync()`);
|
||||
}
|
||||
|
||||
async tick(time: number | string): Promise<number> {
|
||||
this._assertInstalled();
|
||||
await this._browserContext.addInitScript(`globalThis.__pwFakeTimers.tick(${JSON.stringify(time)})`);
|
||||
return await this._evaluateInFrames(`globalThis.__pwFakeTimers.tickAsync(${JSON.stringify(time)})`);
|
||||
}
|
||||
|
||||
private async _addAndEvaluate(script: string) {
|
||||
await this._browserContext.addInitScript(script);
|
||||
return await this._evaluateInFrames(script);
|
||||
|
|
@ -68,12 +97,59 @@ export class Clock {
|
|||
|
||||
private async _evaluateInFrames(script: string) {
|
||||
const frames = this._browserContext.pages().map(page => page.frames()).flat();
|
||||
const results = await Promise.all(frames.map(frame => frame.evaluateExpression(script)));
|
||||
const results = await Promise.all(frames.map(async frame => {
|
||||
try {
|
||||
await frame.nonStallingEvaluateInExistingContext(script, false, 'main');
|
||||
} catch (e) {
|
||||
if (isJavaScriptErrorInEvaluate(e))
|
||||
throw e;
|
||||
}
|
||||
}));
|
||||
return results[0];
|
||||
}
|
||||
|
||||
private _assertInstalled() {
|
||||
if (!this._installed)
|
||||
throw new Error('Clock is not installed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse strings like '01:10:00' (meaning 1 hour, 10 minutes, 0 seconds) into
|
||||
* number of milliseconds. This is used to support human-readable strings passed
|
||||
* to clock.tick()
|
||||
*/
|
||||
function parseTicks(value: number | string): number {
|
||||
if (typeof value === 'number')
|
||||
return value;
|
||||
if (!value)
|
||||
return 0;
|
||||
const str = value;
|
||||
|
||||
const strings = str.split(':');
|
||||
const l = strings.length;
|
||||
let i = l;
|
||||
let ms = 0;
|
||||
let parsed;
|
||||
|
||||
if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
|
||||
throw new Error(
|
||||
`Clock only understands numbers, 'mm:ss' and 'hh:mm:ss'`,
|
||||
);
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
parsed = parseInt(strings[i], 10);
|
||||
if (parsed >= 60)
|
||||
throw new Error(`Invalid time ${str}`);
|
||||
ms += parsed * Math.pow(60, l - i - 1);
|
||||
}
|
||||
|
||||
return ms * 1000;
|
||||
}
|
||||
|
||||
function parseTime(epoch: string | number | undefined): number {
|
||||
if (!epoch)
|
||||
return 0;
|
||||
if (typeof epoch === 'number')
|
||||
return epoch;
|
||||
const parsed = new Date(epoch);
|
||||
if (!isFinite(parsed.getTime()))
|
||||
throw new Error(`Invalid date: ${epoch}`);
|
||||
return parsed.getTime();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Galaxy S5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 740
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 740,
|
||||
"height": 360
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 320,
|
||||
"height": 658
|
||||
|
|
@ -165,7 +165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+ landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 658,
|
||||
"height": 320
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 712,
|
||||
"height": 1138
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1138,
|
||||
"height": 712
|
||||
|
|
@ -978,7 +978,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"LG Optimus L70": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -989,7 +989,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"LG Optimus L70 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1000,7 +1000,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 550": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1011,7 +1011,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 550 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1022,7 +1022,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 950": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1033,7 +1033,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 950 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1044,7 +1044,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 800,
|
||||
"height": 1280
|
||||
|
|
@ -1055,7 +1055,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
|
|
@ -1066,7 +1066,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1077,7 +1077,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1088,7 +1088,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1099,7 +1099,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1110,7 +1110,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1121,7 +1121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1132,7 +1132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1143,7 +1143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1154,7 +1154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1165,7 +1165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1176,7 +1176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 600,
|
||||
"height": 960
|
||||
|
|
@ -1187,7 +1187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 960,
|
||||
"height": 600
|
||||
|
|
@ -1242,7 +1242,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Pixel 2": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 731
|
||||
|
|
@ -1253,7 +1253,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 731,
|
||||
"height": 411
|
||||
|
|
@ -1264,7 +1264,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 XL": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 823
|
||||
|
|
@ -1275,7 +1275,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 XL landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 823,
|
||||
"height": 411
|
||||
|
|
@ -1286,7 +1286,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 393,
|
||||
"height": 786
|
||||
|
|
@ -1297,7 +1297,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 786,
|
||||
"height": 393
|
||||
|
|
@ -1308,7 +1308,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 353,
|
||||
"height": 745
|
||||
|
|
@ -1319,7 +1319,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 745,
|
||||
"height": 353
|
||||
|
|
@ -1330,7 +1330,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G)": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 892
|
||||
|
|
@ -1345,7 +1345,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G) landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"height": 892,
|
||||
"width": 412
|
||||
|
|
@ -1360,7 +1360,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 393,
|
||||
"height": 851
|
||||
|
|
@ -1375,7 +1375,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 851,
|
||||
"height": 393
|
||||
|
|
@ -1390,7 +1390,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 915
|
||||
|
|
@ -1405,7 +1405,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 915,
|
||||
"height": 412
|
||||
|
|
@ -1420,7 +1420,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1431,7 +1431,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1442,7 +1442,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Chrome HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1457,7 +1457,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36 Edg/126.0.6478.26",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36 Edg/127.0.6533.17",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1472,7 +1472,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Firefox HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1502,7 +1502,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Desktop Chrome": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1517,7 +1517,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.26 Safari/537.36 Edg/126.0.6478.26",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36 Edg/127.0.6533.17",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1532,7 +1532,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Firefox": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue