Compare commits
16 commits
main
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a32d04f00e | ||
|
|
8b3ba3f34e | ||
|
|
d93029f32f | ||
|
|
0c6217a85f | ||
|
|
f96b487030 | ||
|
|
937424fb59 | ||
|
|
3d2ffd0fe9 | ||
|
|
dba1319300 | ||
|
|
90f959a11c | ||
|
|
13e6e48a2b | ||
|
|
8762407038 | ||
|
|
f9145b1a7e | ||
|
|
2c17b1f0d3 | ||
|
|
d5943def35 | ||
|
|
73fdd2505d | ||
|
|
13f4531da4 |
11
.devcontainer/devcontainer.json
Normal file
11
.devcontainer/devcontainer.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Playwright",
|
||||
"image": "mcr.microsoft.com/playwright:next",
|
||||
"postCreateCommand": "npm install && npm run build && apt-get update && apt-get install -y software-properties-common && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" && apt-get install -y docker-ce-cli",
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash"
|
||||
},
|
||||
"runArgs": [
|
||||
"-v", "/var/run/docker.sock:/var/run/docker.sock"
|
||||
]
|
||||
}
|
||||
21
.eslintignore
Normal file
21
.eslintignore
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
test/assets/modernizr.js
|
||||
/tests/third_party/
|
||||
/packages/*/lib/
|
||||
*.js
|
||||
/packages/playwright-core/src/generated/*
|
||||
/packages/playwright-core/src/third_party/
|
||||
/packages/playwright-core/types/*
|
||||
/packages/playwright-ct-core/src/generated/*
|
||||
/index.d.ts
|
||||
node_modules/
|
||||
browser_patches/*/checkout/
|
||||
browser_patches/chromium/output/
|
||||
**/*.d.ts
|
||||
output/
|
||||
test-results/
|
||||
tests/components/
|
||||
tests/installation/fixture-scripts/
|
||||
examples/
|
||||
DEPS
|
||||
.cache/
|
||||
utils/
|
||||
15
.eslintrc-with-ts-config.js
Normal file
15
.eslintrc-with-ts-config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
extends: "./.eslintrc.js",
|
||||
parserOptions: {
|
||||
ecmaVersion: 9,
|
||||
sourceType: "module",
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-base-to-string": "error",
|
||||
"@typescript-eslint/no-unnecessary-boolean-literal-compare": 2,
|
||||
},
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json"
|
||||
},
|
||||
};
|
||||
136
.eslintrc.js
Normal file
136
.eslintrc.js
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
module.exports = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint", "notice"],
|
||||
parserOptions: {
|
||||
ecmaVersion: 9,
|
||||
sourceType: "module",
|
||||
},
|
||||
extends: [
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
|
||||
settings: {
|
||||
react: { version: "18" }
|
||||
},
|
||||
|
||||
/**
|
||||
* ESLint rules
|
||||
*
|
||||
* All available rules: http://eslint.org/docs/rules/
|
||||
*
|
||||
* Rules take the following form:
|
||||
* "rule-name", [severity, { opts }]
|
||||
* Severity: 2 == error, 1 == warning, 0 == off.
|
||||
*/
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": [2, {args: "none"}],
|
||||
"@typescript-eslint/consistent-type-imports": [2, {disallowTypeAnnotations: false}],
|
||||
/**
|
||||
* Enforced rules
|
||||
*/
|
||||
// syntax preferences
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"quotes": [2, "single", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"jsx-quotes": [2, "prefer-single"],
|
||||
"no-extra-semi": 2,
|
||||
"@typescript-eslint/semi": [2],
|
||||
"comma-style": [2, "last"],
|
||||
"wrap-iife": [2, "inside"],
|
||||
"spaced-comment": [2, "always", {
|
||||
"markers": ["*"]
|
||||
}],
|
||||
"eqeqeq": [2],
|
||||
"accessor-pairs": [2, {
|
||||
"getWithoutSet": false,
|
||||
"setWithoutGet": false
|
||||
}],
|
||||
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
|
||||
"curly": [2, "multi-or-nest", "consistent"],
|
||||
"new-parens": 2,
|
||||
"arrow-parens": [2, "as-needed"],
|
||||
"prefer-const": 2,
|
||||
"quote-props": [2, "consistent"],
|
||||
"nonblock-statement-body-position": [2, "below"],
|
||||
|
||||
// anti-patterns
|
||||
"no-var": 2,
|
||||
"no-with": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-caller": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-labels": 2,
|
||||
"no-new-object": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-debugger": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-unreachable": 2,
|
||||
"no-unsafe-negation": 2,
|
||||
"radix": 2,
|
||||
"valid-typeof": 2,
|
||||
"no-implicit-globals": [2],
|
||||
"no-unused-expressions": [2, { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true}],
|
||||
"no-proto": 2,
|
||||
|
||||
// es2015 features
|
||||
"require-yield": 2,
|
||||
"template-curly-spacing": [2, "never"],
|
||||
|
||||
// spacing details
|
||||
"space-infix-ops": 2,
|
||||
"space-in-parens": [2, "never"],
|
||||
"array-bracket-spacing": [2, "never"],
|
||||
"comma-spacing": [2, { "before": false, "after": true }],
|
||||
"keyword-spacing": [2, "always"],
|
||||
"space-before-function-paren": [2, {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"no-whitespace-before-property": 2,
|
||||
"keyword-spacing": [2, {
|
||||
"overrides": {
|
||||
"if": {"after": true},
|
||||
"else": {"after": true},
|
||||
"for": {"after": true},
|
||||
"while": {"after": true},
|
||||
"do": {"after": true},
|
||||
"switch": {"after": true},
|
||||
"return": {"after": true}
|
||||
}
|
||||
}],
|
||||
"arrow-spacing": [2, {
|
||||
"after": true,
|
||||
"before": true
|
||||
}],
|
||||
"@typescript-eslint/func-call-spacing": 2,
|
||||
"@typescript-eslint/type-annotation-spacing": 2,
|
||||
|
||||
// file whitespace
|
||||
"no-multiple-empty-lines": [2, {"max": 2}],
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ],
|
||||
"indent": [2, 2, { "SwitchCase": 1, "CallExpression": {"arguments": 2}, "MemberExpression": 2 }],
|
||||
"key-spacing": [2, {
|
||||
"beforeColon": false
|
||||
}],
|
||||
|
||||
// copyright
|
||||
"notice/notice": [2, {
|
||||
"mustMatch": "Copyright",
|
||||
"templateFile": require("path").join(__dirname, "utils", "copyright.js"),
|
||||
}],
|
||||
|
||||
// react
|
||||
"react/react-in-jsx-scope": 0
|
||||
}
|
||||
};
|
||||
|
|
@ -14,12 +14,12 @@ runs:
|
|||
fi
|
||||
echo "Allowing microphone access to all apps"
|
||||
version=$(sw_vers -productVersion | cut -d. -f1)
|
||||
if [[ "$version" == "14" || "$version" == "15" ]]; then
|
||||
if [[ "$version" == "14" ]]; then
|
||||
sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159,NULL,NULL,'UNUSED',1687786159);"
|
||||
elif [[ "$version" == "12" || "$version" == "13" ]]; then
|
||||
sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR REPLACE INTO access VALUES('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159);"
|
||||
else
|
||||
echo "Skipping unsupported macOS version $version"
|
||||
exit 0
|
||||
echo "macOS version is unsupported. Version is $version, exiting"
|
||||
exit 1
|
||||
fi
|
||||
echo "Successfully allowed microphone access"
|
||||
|
|
|
|||
14
.github/dependabot.yml
vendored
14
.github/dependabot.yml
vendored
|
|
@ -1,14 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
21
.github/workflows/create_test_report.yml
vendored
21
.github/workflows/create_test_report.yml
vendored
|
|
@ -22,7 +22,6 @@ jobs:
|
|||
env:
|
||||
DEBUG: pw:install
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
- run: npm run build
|
||||
|
||||
- name: Download blob report artifact
|
||||
|
|
@ -35,7 +34,7 @@ jobs:
|
|||
run: |
|
||||
npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
- name: Azure Login
|
||||
uses: azure/login@v2
|
||||
|
|
@ -121,3 +120,21 @@ jobs:
|
|||
]),
|
||||
});
|
||||
core.info('Posted comment: ' + response.html_url);
|
||||
|
||||
const check = await github.rest.checks.create({
|
||||
...context.repo,
|
||||
name: 'Merge report (${{ github.event.workflow_run.name }})',
|
||||
head_sha: '${{ github.event.workflow_run.head_sha }}',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
details_url: reportUrl,
|
||||
output: {
|
||||
title: 'Test results for "${{ github.event.workflow_run.name }}"',
|
||||
summary: [
|
||||
reportMd,
|
||||
'',
|
||||
'---',
|
||||
`Full [HTML report](${reportUrl}). Merge [workflow run](${mergeWorkflowUrl}).`
|
||||
].join('\n'),
|
||||
}
|
||||
});
|
||||
|
|
|
|||
16
.github/workflows/infra.yml
vendored
16
.github/workflows/infra.yml
vendored
|
|
@ -16,7 +16,7 @@ env:
|
|||
jobs:
|
||||
doc-and-lint:
|
||||
name: "docs & lint"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
|
@ -35,27 +35,21 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
- name: Audit prod NPM dependencies
|
||||
run: node utils/check_audit.js
|
||||
run: npm audit --omit dev
|
||||
lint-snippets:
|
||||
name: "Lint snippets"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '21'
|
||||
- run: npm ci
|
||||
- run: pip install -r utils/doclint/linting-code-snippets/python/requirements.txt
|
||||
- run: mvn package
|
||||
working-directory: utils/doclint/linting-code-snippets/java
|
||||
- run: node utils/doclint/linting-code-snippets/cli.js
|
||||
|
|
|
|||
2
.github/workflows/merge.config.ts
vendored
2
.github/workflows/merge.config.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
testDir: '../../tests',
|
||||
reporter: [[require.resolve('../../packages/playwright/lib/reporters/markdown')], ['html']]
|
||||
reporter: [['markdown'], ['html']]
|
||||
};
|
||||
|
|
@ -12,7 +12,7 @@ on:
|
|||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'microsoft/playwright'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -28,10 +28,6 @@ jobs:
|
|||
commit_sha: context.sha,
|
||||
});
|
||||
const commitHeader = data.message.split('\n')[0];
|
||||
const prMatch = commitHeader.match(/#(\d+)/);
|
||||
const formattedCommit = prMatch
|
||||
? `https://github.com/microsoft/playwright/pull/${prMatch[1]}`
|
||||
: `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha} (${commitHeader})`;
|
||||
|
||||
const title = '[Ports]: Backport client side changes for ' + currentPlaywrightVersion;
|
||||
for (const repo of ['playwright-python', 'playwright-java', 'playwright-dotnet']) {
|
||||
|
|
@ -54,7 +50,7 @@ jobs:
|
|||
issueBody = issueCreateData.body;
|
||||
}
|
||||
const newBody = issueBody.trimEnd() + `
|
||||
- [ ] ${formattedCommit}`;
|
||||
- [ ] https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha} (${commitHeader})`;
|
||||
const data = await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: repo,
|
||||
|
|
|
|||
2
.github/workflows/publish_canary.yml
vendored
2
.github/workflows/publish_canary.yml
vendored
|
|
@ -65,7 +65,7 @@ jobs:
|
|||
|
||||
publish-trace-viewer:
|
||||
name: "publish Trace Viewer to trace.playwright.dev"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'microsoft/playwright'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
|||
2
.github/workflows/publish_release_npm.yml
vendored
2
.github/workflows/publish_release_npm.yml
vendored
|
|
@ -10,7 +10,7 @@ env:
|
|||
jobs:
|
||||
publish-npm-release:
|
||||
name: "publish to NPM"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'microsoft/playwright'
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ on:
|
|||
jobs:
|
||||
publish-trace-viewer:
|
||||
name: "publish Trace Viewer to trace.playwright.dev"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'microsoft/playwright'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
|||
|
|
@ -3,28 +3,16 @@ name: Roll Browser into Playwright
|
|||
on:
|
||||
repository_dispatch:
|
||||
types: [roll_into_pw]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
browser:
|
||||
description: 'Browser name, e.g. chromium'
|
||||
required: true
|
||||
type: string
|
||||
revision:
|
||||
description: 'Browser revision without v prefix, e.g. 1234'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
BROWSER: ${{ github.event.client_payload.browser || github.event.inputs.browser }}
|
||||
REVISION: ${{ github.event.client_payload.revision || github.event.inputs.revision }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
roll:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
|
@ -36,19 +24,19 @@ jobs:
|
|||
run: npx playwright install-deps
|
||||
- name: Roll to new revision
|
||||
run: |
|
||||
./utils/roll_browser.js $BROWSER $REVISION
|
||||
./utils/roll_browser.js ${{ github.event.client_payload.browser }} ${{ github.event.client_payload.revision }}
|
||||
npm run build
|
||||
- name: Prepare branch
|
||||
id: prepare-branch
|
||||
run: |
|
||||
BRANCH_NAME="roll-into-pw-${BROWSER}/${REVISION}"
|
||||
BRANCH_NAME="roll-into-pw-${{ github.event.client_payload.browser }}/${{ github.event.client_payload.revision }}"
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
git config --global user.name github-actions
|
||||
git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
git add .
|
||||
git commit -m "feat(${BROWSER}): roll to r${REVISION}"
|
||||
git push origin $BRANCH_NAME --force
|
||||
git commit -m "feat(${{ github.event.client_payload.browser }}): roll to r${{ github.event.client_payload.revision }}"
|
||||
git push origin $BRANCH_NAME
|
||||
- name: Create Pull Request
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
|
|
@ -59,7 +47,7 @@ jobs:
|
|||
repo: 'playwright',
|
||||
head: 'microsoft:${{ steps.prepare-branch.outputs.BRANCH_NAME }}',
|
||||
base: 'main',
|
||||
title: 'feat(${{ env.BROWSER }}): roll to r${{ env.REVISION }}',
|
||||
title: 'feat(${{ github.event.client_payload.browser }}): roll to r${{ github.event.client_payload.revision }}',
|
||||
});
|
||||
await github.rest.issues.addLabels({
|
||||
owner: 'microsoft',
|
||||
|
|
|
|||
71
.github/workflows/tests_bidi.yml
vendored
71
.github/workflows/tests_bidi.yml
vendored
|
|
@ -1,71 +0,0 @@
|
|||
name: tests BiDi
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .github/workflows/tests_bidi.yml
|
||||
- packages/playwright-core/src/server/bidi/**
|
||||
- tests/bidi/**
|
||||
schedule:
|
||||
# Run every day at midnight
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
test_bidi:
|
||||
name: BiDi
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-24.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:
|
||||
channel: [bidi-chromium, bidi-firefox-nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm ci
|
||||
env:
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
- run: npm run build
|
||||
- run: npx playwright install --with-deps chromium
|
||||
if: matrix.channel == 'bidi-chromium'
|
||||
- run: npx -y @puppeteer/browsers install firefox@nightly
|
||||
if: matrix.channel == 'bidi-firefox-nightly'
|
||||
- name: Run tests
|
||||
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run biditest -- --project=${{ matrix.channel }}*
|
||||
env:
|
||||
PWTEST_USE_BIDI_EXPECTATIONS: '1'
|
||||
- name: Upload csv report to GitHub
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: csv-report-${{ matrix.channel }}
|
||||
path: test-results/report.csv
|
||||
retention-days: 7
|
||||
|
||||
- name: Azure Login
|
||||
if: ${{ !cancelled() && github.ref == 'refs/heads/main' }}
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_BLOB_REPORTS_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_BLOB_REPORTS_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_BLOB_REPORTS_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: Upload report.csv to Azure
|
||||
if: ${{ !cancelled() && github.ref == 'refs/heads/main' }}
|
||||
run: |
|
||||
REPORT_DIR='bidi-reports'
|
||||
azcopy cp "./test-results/report.csv" "https://mspwblobreport.blob.core.windows.net/\$web/$REPORT_DIR/${{ matrix.channel }}.csv"
|
||||
echo "Report url: https://mspwblobreport.z1.web.core.windows.net/$REPORT_DIR/${{ matrix.channel }}.csv"
|
||||
env:
|
||||
AZCOPY_AUTO_LOGIN_TYPE: AZCLI
|
||||
13
.github/workflows/tests_others.yml
vendored
13
.github/workflows/tests_others.yml
vendored
|
|
@ -24,7 +24,9 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
# 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
|
||||
|
|
@ -64,7 +66,7 @@ jobs:
|
|||
contents: read # This is required for actions/checkout to succeed
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-dotnet@v4
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- run: dotnet build
|
||||
|
|
@ -147,13 +149,6 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Ubuntu Binary Installation # TODO: Remove when https://github.com/electron/electron/issues/42510 is fixed
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
if grep -q "Ubuntu 24" /etc/os-release; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
shell: bash
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chromium
|
||||
|
|
|
|||
7
.github/workflows/tests_primary.yml
vendored
7
.github/workflows/tests_primary.yml
vendored
|
|
@ -215,13 +215,6 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- run: npm install -g yarn@1
|
||||
- run: npm install -g pnpm@8
|
||||
- name: Setup Ubuntu Binary Installation # TODO: Remove when https://github.com/electron/electron/issues/42510 is fixed
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
if grep -q "Ubuntu 24" /etc/os-release; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
shell: bash
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
command: npm run itest
|
||||
|
|
|
|||
366
.github/workflows/tests_secondary.yml
vendored
366
.github/workflows/tests_secondary.yml
vendored
|
|
@ -50,15 +50,8 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Intel: *-large
|
||||
# Arm64: *-xlarge
|
||||
os: [macos-13-large, macos-13-xlarge, macos-14-large, macos-14-xlarge]
|
||||
os: [macos-12, macos-13, macos-14]
|
||||
browser: [chromium, firefox, webkit]
|
||||
include:
|
||||
- os: macos-15-large
|
||||
browser: webkit
|
||||
- os: macos-15-xlarge
|
||||
browser: webkit
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -107,13 +100,6 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- run: npm install -g yarn@1
|
||||
- run: npm install -g pnpm@8
|
||||
- name: Setup Ubuntu Binary Installation # TODO: Remove when https://github.com/electron/electron/issues/42510 is fixed
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
if grep -q "Ubuntu 24" /etc/os-release; then
|
||||
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
fi
|
||||
shell: bash
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
|
|
@ -130,13 +116,7 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
browser: [chromium, firefox, webkit]
|
||||
os: [ubuntu-24.04, macos-14-xlarge, windows-latest]
|
||||
include:
|
||||
# We have different binaries per Ubuntu version for WebKit.
|
||||
- browser: webkit
|
||||
os: ubuntu-20.04
|
||||
- browser: webkit
|
||||
os: ubuntu-22.04
|
||||
os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, macos-14, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -197,92 +177,356 @@ jobs:
|
|||
PWTEST_TRACE: 1
|
||||
PWTEST_CHANNEL: ${{ matrix.channel }}
|
||||
|
||||
test_chromium_channels:
|
||||
name: Test ${{ matrix.channel }} on ${{ matrix.runs-on }}
|
||||
chrome_stable_linux:
|
||||
name: "Chrome Stable (Linux)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
channel: [chrome, chrome-beta, msedge, msedge-beta, msedge-dev]
|
||||
runs-on: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: ${{ matrix.channel }}
|
||||
browsers-to-install: chrome
|
||||
command: npm run ctest
|
||||
bot-name: ${{ matrix.channel }}-${{ matrix.runs-on }}
|
||||
bot-name: "chrome-stable-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:
|
||||
PWTEST_CHANNEL: ${{ matrix.channel }}
|
||||
PWTEST_CHANNEL: chrome
|
||||
|
||||
chrome_stable_win:
|
||||
name: "Chrome Stable (Win)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chrome
|
||||
command: npm run ctest
|
||||
bot-name: "chrome-stable-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 }}
|
||||
env:
|
||||
PWTEST_CHANNEL: chrome
|
||||
|
||||
chrome_stable_mac:
|
||||
name: "Chrome Stable (Mac)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chrome
|
||||
command: npm run ctest
|
||||
bot-name: "chrome-stable-mac"
|
||||
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:
|
||||
PWTEST_CHANNEL: chrome
|
||||
|
||||
chromium_tot:
|
||||
name: Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }}
|
||||
name: Chromium tip-of-tree ${{ matrix.os }}
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-13, windows-latest]
|
||||
headed: ['--headed', '']
|
||||
os: [ubuntu-20.04, macos-12, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chromium-tip-of-tree
|
||||
command: npm run ctest -- ${{ matrix.headed }}
|
||||
bot-name: "chromium-tip-of-tree-${{ matrix.os }}${{ matrix.headed }}"
|
||||
command: npm run ctest
|
||||
bot-name: "tip-of-tree-${{ 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:
|
||||
PWTEST_CHANNEL: chromium-tip-of-tree
|
||||
|
||||
chromium_tot_headless_shell:
|
||||
name: Chromium tip-of-tree headless-shell-${{ matrix.os }}
|
||||
chromium_tot_headed:
|
||||
name: Chromium tip-of-tree headed ${{ matrix.os }}
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chromium-tip-of-tree-headless-shell
|
||||
command: npm run ctest
|
||||
bot-name: "chromium-tip-of-tree-headless-shell-${{ matrix.os }}"
|
||||
browsers-to-install: chromium-tip-of-tree
|
||||
command: npm run ctest -- --headed
|
||||
bot-name: "tip-of-tree-headed-${{ 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:
|
||||
PWTEST_CHANNEL: chromium-tip-of-tree-headless-shell
|
||||
PWTEST_CHANNEL: chromium-tip-of-tree
|
||||
|
||||
firefox_beta:
|
||||
name: Firefox Beta ${{ matrix.os }}
|
||||
firefox_beta_linux:
|
||||
name: "Firefox Beta (Linux)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: firefox-beta chromium
|
||||
command: npm run ftest
|
||||
bot-name: "firefox-beta-${{ matrix.os }}"
|
||||
bot-name: "firefox-beta-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:
|
||||
PWTEST_CHANNEL: firefox-beta
|
||||
|
||||
firefox_beta_win:
|
||||
name: "Firefox Beta (Win)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: firefox-beta chromium
|
||||
command: npm run ftest -- --workers=1
|
||||
bot-name: "firefox-beta-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 }}
|
||||
env:
|
||||
PWTEST_CHANNEL: firefox-beta
|
||||
|
||||
firefox_beta_mac:
|
||||
name: "Firefox Beta (Mac)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
# 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
|
||||
with:
|
||||
browsers-to-install: firefox-beta chromium
|
||||
command: npm run ftest
|
||||
bot-name: "firefox-beta-mac"
|
||||
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:
|
||||
PWTEST_CHANNEL: firefox-beta
|
||||
|
||||
edge_stable_mac:
|
||||
name: "Edge Stable (Mac)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge
|
||||
command: npm run ctest
|
||||
bot-name: "edge-stable-mac"
|
||||
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:
|
||||
PWTEST_CHANNEL: msedge
|
||||
|
||||
edge_stable_win:
|
||||
name: "Edge Stable (Win)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge
|
||||
command: npm run ctest
|
||||
bot-name: "edge-stable-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 }}
|
||||
env:
|
||||
PWTEST_CHANNEL: msedge
|
||||
|
||||
edge_stable_linux:
|
||||
name: "Edge Stable (Linux)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge
|
||||
command: npm run ctest
|
||||
bot-name: "edge-stable-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:
|
||||
PWTEST_CHANNEL: msedge
|
||||
|
||||
edge_beta_mac:
|
||||
name: "Edge Beta (Mac)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge-beta
|
||||
command: npm run ctest
|
||||
bot-name: "edge-beta-mac"
|
||||
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:
|
||||
PWTEST_CHANNEL: msedge-beta
|
||||
|
||||
edge_beta_win:
|
||||
name: "Edge Beta (Win)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge-beta
|
||||
command: npm run ctest
|
||||
bot-name: "edge-beta-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 }}
|
||||
env:
|
||||
PWTEST_CHANNEL: msedge-beta
|
||||
|
||||
edge_beta_linux:
|
||||
name: "Edge Beta (Linux)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge-beta
|
||||
command: npm run ctest
|
||||
bot-name: "edge-beta-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:
|
||||
PWTEST_CHANNEL: msedge-beta
|
||||
|
||||
edge_dev_mac:
|
||||
name: "Edge Dev (Mac)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge-dev
|
||||
command: npm run ctest
|
||||
bot-name: "edge-dev-mac"
|
||||
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:
|
||||
PWTEST_CHANNEL: msedge-dev
|
||||
|
||||
edge_dev_win:
|
||||
name: "Edge Dev (Win)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge-dev
|
||||
command: npm run ctest
|
||||
bot-name: "edge-dev-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 }}
|
||||
env:
|
||||
PWTEST_CHANNEL: msedge-dev
|
||||
|
||||
edge_dev_linux:
|
||||
name: "Edge Dev (Linux)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: msedge-dev
|
||||
command: npm run ctest
|
||||
bot-name: "edge-dev-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:
|
||||
PWTEST_CHANNEL: msedge-dev
|
||||
|
||||
chrome_beta_linux:
|
||||
name: "Chrome Beta (Linux)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chrome-beta
|
||||
command: npm run ctest
|
||||
bot-name: "chrome-beta-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:
|
||||
PWTEST_CHANNEL: chrome-beta
|
||||
|
||||
chrome_beta_win:
|
||||
name: "Chrome Beta (Win)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chrome-beta
|
||||
command: npm run ctest
|
||||
bot-name: "chrome-beta-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 }}
|
||||
env:
|
||||
PWTEST_CHANNEL: chrome-beta
|
||||
|
||||
chrome_beta_mac:
|
||||
name: "Chrome Beta (Mac)"
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
browsers-to-install: chrome-beta
|
||||
command: npm run ctest
|
||||
bot-name: "chrome-beta-mac"
|
||||
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:
|
||||
PWTEST_CHANNEL: chrome-beta
|
||||
|
||||
build-playwright-driver:
|
||||
name: "build-playwright-driver"
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
@ -296,25 +540,19 @@ jobs:
|
|||
- run: npx playwright install-deps
|
||||
- run: utils/build/build-playwright-driver.sh
|
||||
|
||||
test_channel_chromium:
|
||||
name: Test channel=chromium
|
||||
test_linux_chromium_headless_new:
|
||||
name: Linux Chromium Headless New
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runs-on: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
# TODO: this should pass --no-shell.
|
||||
# However, codegen tests do not inherit the channel and try to launch headless shell.
|
||||
browsers-to-install: chromium
|
||||
command: npm run ctest
|
||||
bot-name: "channel-chromium-${{ matrix.runs-on }}"
|
||||
bot-name: "headless-new"
|
||||
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:
|
||||
PWTEST_CHANNEL: chromium
|
||||
PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW: 1
|
||||
|
|
|
|||
2
.github/workflows/tests_service.yml
vendored
2
.github/workflows/tests_service.yml
vendored
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
PLAYWRIGHT_SERVICE_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }}
|
||||
- name: Upload blob report to GitHub
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: all-blob-reports
|
||||
path: blob-report
|
||||
|
|
|
|||
2
.github/workflows/trigger_tests.yml
vendored
2
.github/workflows/trigger_tests.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
|||
jobs:
|
||||
trigger:
|
||||
name: "trigger"
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: |
|
||||
curl -X POST \
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -35,4 +35,3 @@ test-results
|
|||
.cache/
|
||||
.eslintcache
|
||||
playwright.env
|
||||
/firefox/
|
||||
|
|
|
|||
235
CONTRIBUTING.md
235
CONTRIBUTING.md
|
|
@ -1,87 +1,92 @@
|
|||
# Contributing
|
||||
|
||||
## Choose an issue
|
||||
- [How to Contribute](#how-to-contribute)
|
||||
* [Getting Code](#getting-code)
|
||||
* [Code reviews](#code-reviews)
|
||||
* [Code Style](#code-style)
|
||||
* [API guidelines](#api-guidelines)
|
||||
* [Commit Messages](#commit-messages)
|
||||
* [Writing Documentation](#writing-documentation)
|
||||
* [Adding New Dependencies](#adding-new-dependencies)
|
||||
* [Running & Writing Tests](#running--writing-tests)
|
||||
* [Public API Coverage](#public-api-coverage)
|
||||
- [Contributor License Agreement](#contributor-license-agreement)
|
||||
* [Code of Conduct](#code-of-conduct)
|
||||
|
||||
Playwright **requires an issue** for every contribution, except for minor documentation updates. We strongly recommend to pick an issue labeled `open-to-a-pull-request` for your first contribution to the project.
|
||||
## How to Contribute
|
||||
|
||||
If you are passioned about a bug/feature, but cannot find an issue describing it, **file an issue first**. This will facilitate the discussion and you might get some early feedback from project maintainers before spending your time on creating a pull request.
|
||||
We strongly recommend that you open an issue before beginning any code modifications. This is particularly important if the changes involve complex logic or if the existing code isn't immediately clear. By doing so, we can discuss and agree upon the best approach to address a bug or implement a feature, ensuring that our efforts are aligned.
|
||||
|
||||
## Make a change
|
||||
### Getting Code
|
||||
|
||||
Make sure you're running Node.js 20 to verify and upgrade NPM do:
|
||||
|
||||
Make sure you're running Node.js 20 or later.
|
||||
```bash
|
||||
node --version
|
||||
npm --version
|
||||
npm i -g npm@latest
|
||||
```
|
||||
|
||||
Clone the repository. If you plan to send a pull request, it might be better to [fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) first.
|
||||
1. Clone this repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/microsoft/playwright
|
||||
cd playwright
|
||||
```
|
||||
|
||||
Install dependencies and run the build in watch mode.
|
||||
2. Install dependencies
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run watch
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
**Experimental dev mode with Hot Module Replacement for recorder/trace-viewer/UI Mode**
|
||||
3. Build Playwright
|
||||
|
||||
```
|
||||
PW_HMR=1 npm run watch
|
||||
PW_HMR=1 npx playwright show-trace
|
||||
PW_HMR=1 npm run ctest -- --ui
|
||||
PW_HMR=1 npx playwright codegen
|
||||
PW_HMR=1 npx playwright show-report
|
||||
```
|
||||
|
||||
Playwright is a multi-package repository that uses npm workspaces. For browser APIs, look at [`packages/playwright-core`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core). For test runner, see [`packages/playwright`](https://github.com/microsoft/playwright/blob/main/packages/playwright).
|
||||
|
||||
Note that some files are generated by the build, so the watch process might override your changes if done in the wrong file. For example, TypeScript types for the API are generated from the [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src).
|
||||
|
||||
Coding style is fully defined in [.eslintrc](https://github.com/microsoft/playwright/blob/main/.eslintrc.js). Before creating a pull request, or at any moment during development, run linter to check all kinds of things:
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
|
||||
Comments should have an explicit purpose and should improve readability rather than hinder it. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.
|
||||
4. Run tests
|
||||
|
||||
### Write documentation
|
||||
This will run a test on line `23` in `page-fill.spec.ts`:
|
||||
|
||||
Every part of the public API should be documented in [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src), in the same change that adds/changes the API. We use markdown files with custom structure to specify the API. Take a look around for an example.
|
||||
|
||||
Various other files are generated from the API specification. If you are running `npm run watch`, these will be re-generated automatically.
|
||||
|
||||
Larger changes will require updates to the documentation guides as well. This will be made clear during the code review.
|
||||
|
||||
## Add a test
|
||||
|
||||
Playwright requires a test for almost any new or modified functionality. An exception would be a pure refactoring, but chances are you are doing more than that.
|
||||
|
||||
There are multiple [test suites](https://github.com/microsoft/playwright/blob/main/tests) in Playwright that will be executed on the CI. The two most important that you need to run locally are:
|
||||
|
||||
- Library tests cover APIs not related to the test runner.
|
||||
```bash
|
||||
# fast path runs all tests in Chromium
|
||||
npm run ctest
|
||||
|
||||
# slow path runs all tests in three browsers
|
||||
npm run test
|
||||
npm run ctest -- page-fill:23
|
||||
```
|
||||
|
||||
- Test runner tests.
|
||||
See [here](#running--writing-tests) for more information about running and writing tests.
|
||||
|
||||
### Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
### Code Style
|
||||
|
||||
- Coding style is fully defined in [.eslintrc](https://github.com/microsoft/playwright/blob/main/.eslintrc.js)
|
||||
- Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.
|
||||
|
||||
To run code linter, use:
|
||||
|
||||
```bash
|
||||
npm run ttest
|
||||
npm run eslint
|
||||
```
|
||||
|
||||
Since Playwright tests are using Playwright under the hood, everything from our documentation applies, for example [this guide on running and debugging tests](https://playwright.dev/docs/running-tests#running-tests).
|
||||
### API guidelines
|
||||
|
||||
Note that tests should be *hermetic*, and not depend on external services. Tests should work on all three platforms: macOS, Linux and Windows.
|
||||
When authoring new API methods, consider the following:
|
||||
|
||||
## Write a commit message
|
||||
- Expose as little information as needed. When in doubt, don’t expose new information.
|
||||
- Methods are used in favor of getters/setters.
|
||||
- The only exception is namespaces, e.g. `page.keyboard` and `page.coverage`
|
||||
- All string literals must be lowercase. This includes event names and option values.
|
||||
- Avoid adding "sugar" API (API that is trivially implementable in user-space) unless they're **very** common.
|
||||
|
||||
Commit messages should follow the [Semantic Commit Messages](https://www.conventionalcommits.org/en/v1.0.0/) format:
|
||||
### Commit Messages
|
||||
|
||||
Commit messages should follow the Semantic Commit Messages format:
|
||||
|
||||
```
|
||||
label(namespace): title
|
||||
|
|
@ -92,57 +97,129 @@ footer
|
|||
```
|
||||
|
||||
1. *label* is one of the following:
|
||||
- `fix` - bug fixes
|
||||
- `feat` - new features
|
||||
- `docs` - documentation-only changes
|
||||
- `test` - test-only changes
|
||||
- `devops` - changes to the CI or build
|
||||
- `fix` - playwright bug fixes.
|
||||
- `feat` - playwright features.
|
||||
- `docs` - changes to docs, e.g. `docs(api): ..` to change documentation.
|
||||
- `test` - changes to playwright tests infrastructure.
|
||||
- `devops` - build-related work, e.g. CI related patches and general changes to the browser build infrastructure
|
||||
- `chore` - everything that doesn't fall under previous categories
|
||||
1. *namespace* is put in parenthesis after label and is optional. Must be lowercase.
|
||||
1. *title* is a brief summary of changes.
|
||||
1. *description* is **optional**, new-line separated from title and is in present tense.
|
||||
1. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues.
|
||||
2. *namespace* is put in parenthesis after label and is optional. Must be lowercase.
|
||||
3. *title* is a brief summary of changes.
|
||||
4. *description* is **optional**, new-line separated from title and is in present tense.
|
||||
5. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
feat(trace viewer): network panel filtering
|
||||
fix(firefox): make sure session cookies work
|
||||
|
||||
This patch adds a filtering toolbar to the network panel.
|
||||
<link to a screenshot>
|
||||
This patch fixes session cookies in the firefox browser.
|
||||
|
||||
Fixes #123, references #234.
|
||||
Fixes #123, fixes #234
|
||||
```
|
||||
|
||||
## Send a pull request
|
||||
### Writing Documentation
|
||||
|
||||
All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests.
|
||||
All API classes, methods, and events should have a description in [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src). There's a [documentation linter](https://github.com/microsoft/playwright/tree/main/utils/doclint) which makes sure documentation is aligned with the codebase.
|
||||
|
||||
After a successful code review, one of the maintainers will merge your pull request. Congratulations!
|
||||
To run the documentation linter, use:
|
||||
|
||||
## More details
|
||||
```bash
|
||||
npm run doc
|
||||
```
|
||||
|
||||
**No new dependencies**
|
||||
To build the documentation site locally and test how your changes will look in practice:
|
||||
|
||||
There is a very high bar for new dependencies, including updating to a new version of an existing dependency. We recommend to explicitly discuss this in an issue and get a green light from a maintainer, before creating a pull request that updates dependencies.
|
||||
1. Clone the [microsoft/playwright.dev](https://github.com/microsoft/playwright.dev) repo
|
||||
1. Follow [the playwright.dev README instructions to "roll docs"](https://github.com/microsoft/playwright.dev/#roll-docs) against your local `playwright` repo with your changes in progress
|
||||
1. Follow [the playwright.dev README instructions to "run dev server"](https://github.com/microsoft/playwright.dev/#run-dev-server) to view your changes
|
||||
|
||||
**Custom browser build**
|
||||
### Adding New Dependencies
|
||||
|
||||
For all dependencies (both installation and development):
|
||||
- **Do not add** a dependency if the desired functionality is easily implementable.
|
||||
- If adding a dependency, it should be well-maintained and trustworthy.
|
||||
|
||||
A barrier for introducing new installation dependencies is especially high:
|
||||
- **Do not add** installation dependency unless it's critical to project success.
|
||||
|
||||
### Running & Writing Tests
|
||||
|
||||
- Every feature should be accompanied by a test.
|
||||
- Every public api event/method should be accompanied by a test.
|
||||
- Tests should be *hermetic*. Tests should not depend on external services.
|
||||
- Tests should work on all three platforms: Mac, Linux and Win. This is especially important for screenshot tests.
|
||||
|
||||
Playwright tests are located in [`tests`](https://github.com/microsoft/playwright/blob/main/tests) and use `@playwright/test` test runner.
|
||||
These are integration tests, making sure public API methods and events work as expected.
|
||||
|
||||
- To run all tests:
|
||||
|
||||
```bash
|
||||
npx playwright install
|
||||
npm run test
|
||||
```
|
||||
|
||||
Be sure to run `npm run build` or let `npm run watch` run before you re-run the
|
||||
tests after making your changes to check them.
|
||||
|
||||
- To run tests in Chromium
|
||||
```bash
|
||||
npm run ctest # also `ftest` for firefox and `wtest` for WebKit
|
||||
npm run ctest -- page-fill:23 # runs line 23 of page-fill.spec.ts
|
||||
```
|
||||
|
||||
To run tests in WebKit / Firefox, use `wtest` or `ftest`.
|
||||
|
||||
- To run the Playwright test runner tests
|
||||
```bash
|
||||
npm run ttest
|
||||
npm run ttest -- --grep "specific test"
|
||||
```
|
||||
|
||||
- To run a specific test, substitute `it` with `it.only`, or use the `--grep 'My test'` CLI parameter:
|
||||
|
||||
```js
|
||||
...
|
||||
// Using "it.only" to run a specific test
|
||||
it.only('should work', async ({server, page}) => {
|
||||
const response = await page.goto(server.EMPTY_PAGE);
|
||||
expect(response.ok).toBe(true);
|
||||
});
|
||||
// or
|
||||
playwright test --config=xxx --grep 'should work'
|
||||
```
|
||||
|
||||
- To disable a specific test, substitute `it` with `it.skip`:
|
||||
|
||||
```js
|
||||
...
|
||||
// Using "it.skip" to skip a specific test
|
||||
it.skip('should work', async ({server, page}) => {
|
||||
const response = await page.goto(server.EMPTY_PAGE);
|
||||
expect(response.ok).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
- To run tests in non-headless (headed) mode:
|
||||
|
||||
```bash
|
||||
npm run ctest -- --headed
|
||||
```
|
||||
|
||||
- To run tests with custom browser executable, specify `CRPATH`, `WKPATH` or `FFPATH` env variable that points to browser executable:
|
||||
|
||||
To run tests with custom browser executable, specify `CRPATH`, `WKPATH` or `FFPATH` env variable that points to browser executable:
|
||||
```bash
|
||||
CRPATH=<path-to-executable> npm run ctest
|
||||
```
|
||||
|
||||
You will also find `DEBUG=pw:browser` useful for debugging custom builds.
|
||||
- When should a test be marked with `skip` or `fixme`?
|
||||
|
||||
**Building documentation site**
|
||||
- **`skip(condition)`**: This test *should ***never*** work* for `condition`
|
||||
where `condition` is usually something like: `test.skip(browserName === 'chromium', 'This does not work because of ...')`.
|
||||
|
||||
The [playwright.dev](https://playwright.dev/) documentation site lives in a separate repository, and documentation from [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src) is frequently rolled there.
|
||||
|
||||
Most of the time this should not concern you. However, if you are doing something unusual in the docs, you can build locally and test how your changes will look in practice:
|
||||
1. Clone the [microsoft/playwright.dev](https://github.com/microsoft/playwright.dev) repo.
|
||||
1. Follow [the playwright.dev README instructions to "roll docs"](https://github.com/microsoft/playwright.dev/#roll-docs) against your local `playwright` repo with your changes in progress.
|
||||
1. Follow [the playwright.dev README instructions to "run dev server"](https://github.com/microsoft/playwright.dev/#run-dev-server) to view your changes.
|
||||
- **`fixme(condition)`**: This test *should ***eventually*** work* for `condition`
|
||||
where `condition` is usually something like: `test.fixme(browserName === 'chromium', 'We are waiting for version x')`.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
# How to File a Bug Report That Actually Gets Resolved
|
||||
|
||||
Make sure you’re on the latest Playwright release before filing. Check existing GitHub issues to avoid duplicates.
|
||||
|
||||
## Use the Template
|
||||
|
||||
Follow the **Bug Report** template. It guides you step-by-step:
|
||||
|
||||
- Fill it out thoroughly.
|
||||
- Clearly list the steps needed to reproduce the bug.
|
||||
- Provide what you expected to see versus what happened in reality.
|
||||
- Include system info from `npx envinfo --preset playwright`.
|
||||
|
||||
## Keep Your Repro Minimal
|
||||
|
||||
We can't parse your entire code base. Reduce it down to the absolute essentials:
|
||||
|
||||
- Start a fresh project (`npm init playwright@latest new-project`).
|
||||
- Add only the code/DOM needed to show the problem.
|
||||
- Only use major frameworks if necessary (React, Angular, static HTTP server, etc.).
|
||||
- Avoid adding extra libraries unless absolutely necessary. Note that we won't install any suspect dependencies.
|
||||
|
||||
## Why This Matters
|
||||
- Most issues that lack a repro turn out to be misconfigurations or usage errors.
|
||||
- We can't fix problems if we can’t reproduce them ourselves.
|
||||
- We can’t debug entire private projects or handle sensitive credentials.
|
||||
- Each confirmed bug will have a test in our repo, so your repro must be as clean as possible.
|
||||
|
||||
## More Help
|
||||
|
||||
- [Stack Overflow’s Minimal Reproducible Example Guide](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||
- [Playwright Debugging Tools](https://playwright.dev/docs/debug)
|
||||
|
||||
## Bottom Line
|
||||
A well-isolated bug speeds up verification and resolution. Minimal, public repro or it’s unlikely we can assist.
|
||||
11
README.md
11
README.md
|
|
@ -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://aka.ms/playwright/discord)
|
||||
[](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://aka.ms/playwright/discord)
|
||||
|
||||
## [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 -->134.0.6998.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->135.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->129.0.6668.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->130.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.
|
||||
|
||||
|
|
@ -46,6 +46,7 @@ npx playwright install
|
|||
You can optionally install only selected browsers, see [install browsers](https://playwright.dev/docs/cli#install-browsers) for more details. Or you can install no browsers at all and use existing [browser channels](https://playwright.dev/docs/browsers).
|
||||
|
||||
* [Getting started](https://playwright.dev/docs/intro)
|
||||
* [Installation configuration](https://playwright.dev/docs/installation)
|
||||
* [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
## Capabilities
|
||||
|
|
@ -162,7 +163,7 @@ test('Intercept network requests', async ({ page }) => {
|
|||
|
||||
## Resources
|
||||
|
||||
* [Documentation](https://playwright.dev)
|
||||
* [Documentation](https://playwright.dev/docs/intro)
|
||||
* [API reference](https://playwright.dev/docs/api/class-playwright/)
|
||||
* [Contribution guide](CONTRIBUTING.md)
|
||||
* [Changelog](https://github.com/microsoft/playwright/releases)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/mozilla/gecko-dev"
|
||||
BASE_BRANCH="release"
|
||||
BASE_REVISION="5cfa81898f6eef8fb1abe463e5253cea5bc17f3f"
|
||||
BASE_REVISION="4c9a3f8e2db68ae0a8fcf6bbf0574e3c0549ff49"
|
||||
|
|
|
|||
|
|
@ -145,13 +145,10 @@ class NetworkRequest {
|
|||
}
|
||||
this._expectingInterception = false;
|
||||
this._expectingResumedRequest = undefined; // { method, headers, postData }
|
||||
this._overriddenHeadersForRedirect = redirectedFrom?._overriddenHeadersForRedirect;
|
||||
this._sentOnResponse = false;
|
||||
this._fulfilled = false;
|
||||
|
||||
if (this._overriddenHeadersForRedirect)
|
||||
overrideRequestHeaders(httpChannel, this._overriddenHeadersForRedirect);
|
||||
else if (this._pageNetwork)
|
||||
if (this._pageNetwork)
|
||||
appendExtraHTTPHeaders(httpChannel, this._pageNetwork.combinedExtraHTTPHeaders());
|
||||
|
||||
this._responseBodyChunks = [];
|
||||
|
|
@ -233,13 +230,20 @@ class NetworkRequest {
|
|||
if (!this._expectingResumedRequest)
|
||||
return;
|
||||
const { method, headers, postData } = this._expectingResumedRequest;
|
||||
this._overriddenHeadersForRedirect = headers;
|
||||
this._expectingResumedRequest = undefined;
|
||||
|
||||
if (headers)
|
||||
overrideRequestHeaders(this.httpChannel, headers);
|
||||
else if (this._pageNetwork)
|
||||
if (headers) {
|
||||
for (const header of requestHeaders(this.httpChannel)) {
|
||||
// We cannot remove the "host" header.
|
||||
if (header.name.toLowerCase() === 'host')
|
||||
continue;
|
||||
this.httpChannel.setRequestHeader(header.name, '', false /* merge */);
|
||||
}
|
||||
for (const header of headers)
|
||||
this.httpChannel.setRequestHeader(header.name, header.value, false /* merge */);
|
||||
} else if (this._pageNetwork) {
|
||||
appendExtraHTTPHeaders(this.httpChannel, this._pageNetwork.combinedExtraHTTPHeaders());
|
||||
}
|
||||
if (method)
|
||||
this.httpChannel.requestMethod = method;
|
||||
if (postData !== undefined)
|
||||
|
|
@ -598,8 +602,6 @@ class NetworkObserver {
|
|||
proxyFilter.onProxyFilterResult(defaultProxyInfo);
|
||||
return;
|
||||
}
|
||||
if (this._targetRegistry.shouldBustHTTPAuthCacheForProxy(proxy))
|
||||
Services.obs.notifyObservers(null, "net:clear-active-logins");
|
||||
proxyFilter.onProxyFilterResult(protocolProxyService.newProxyInfo(
|
||||
proxy.type,
|
||||
proxy.host,
|
||||
|
|
@ -769,20 +771,6 @@ function requestHeaders(httpChannel) {
|
|||
return headers;
|
||||
}
|
||||
|
||||
function clearRequestHeaders(httpChannel) {
|
||||
for (const header of requestHeaders(httpChannel)) {
|
||||
// We cannot remove the "host" header.
|
||||
if (header.name.toLowerCase() === 'host')
|
||||
continue;
|
||||
httpChannel.setRequestHeader(header.name, '', false /* merge */);
|
||||
}
|
||||
}
|
||||
|
||||
function overrideRequestHeaders(httpChannel, headers) {
|
||||
clearRequestHeaders(httpChannel);
|
||||
appendExtraHTTPHeaders(httpChannel, headers);
|
||||
}
|
||||
|
||||
function causeTypeToString(causeType) {
|
||||
for (let key in Ci.nsIContentPolicy) {
|
||||
if (Ci.nsIContentPolicy[key] === causeType)
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ class TargetRegistry {
|
|||
this._browserToTarget = new Map();
|
||||
this._browserIdToTarget = new Map();
|
||||
|
||||
this._proxiesWithClashingAuthCacheKeys = new Set();
|
||||
this._browserProxy = null;
|
||||
|
||||
// Cleanup containers from previous runs (if any)
|
||||
|
|
@ -235,50 +234,12 @@ class TargetRegistry {
|
|||
onOpenWindow(win);
|
||||
}
|
||||
|
||||
// Firefox uses nsHttpAuthCache to cache authentication to the proxy.
|
||||
// If we're provided with a single proxy with a multiple different authentications, then
|
||||
// we should clear the nsHttpAuthCache on every request.
|
||||
shouldBustHTTPAuthCacheForProxy(proxy) {
|
||||
return this._proxiesWithClashingAuthCacheKeys.has(proxy);
|
||||
}
|
||||
|
||||
_updateProxiesWithSameAuthCacheAndDifferentCredentials() {
|
||||
const proxyIdToCredentials = new Map();
|
||||
const allProxies = [...this._browserContextIdToBrowserContext.values()].map(bc => bc._proxy).filter(Boolean);
|
||||
if (this._browserProxy)
|
||||
allProxies.push(this._browserProxy);
|
||||
const proxyAuthCacheKeyAndProxy = allProxies.map(proxy => [
|
||||
JSON.stringify({
|
||||
type: proxy.type,
|
||||
host: proxy.host,
|
||||
port: proxy.port,
|
||||
}),
|
||||
proxy,
|
||||
]);
|
||||
this._proxiesWithClashingAuthCacheKeys.clear();
|
||||
|
||||
proxyAuthCacheKeyAndProxy.sort(([cacheKey1], [cacheKey2]) => cacheKey1 < cacheKey2 ? -1 : 1);
|
||||
for (let i = 0; i < proxyAuthCacheKeyAndProxy.length - 1; ++i) {
|
||||
const [cacheKey1, proxy1] = proxyAuthCacheKeyAndProxy[i];
|
||||
const [cacheKey2, proxy2] = proxyAuthCacheKeyAndProxy[i + 1];
|
||||
if (cacheKey1 !== cacheKey2)
|
||||
continue;
|
||||
if (proxy1.username === proxy2.username && proxy1.password === proxy2.password)
|
||||
continue;
|
||||
// `proxy1` and `proxy2` have the same caching key, but serve different credentials.
|
||||
// We have to bust HTTP Auth Cache everytime there's a request that will use either of the proxies.
|
||||
this._proxiesWithClashingAuthCacheKeys.add(proxy1);
|
||||
this._proxiesWithClashingAuthCacheKeys.add(proxy2);
|
||||
}
|
||||
}
|
||||
|
||||
async cancelDownload(options) {
|
||||
this._downloadInterceptor.cancelDownload(options.uuid);
|
||||
}
|
||||
|
||||
setBrowserProxy(proxy) {
|
||||
this._browserProxy = proxy;
|
||||
this._updateProxiesWithSameAuthCacheAndDifferentCredentials();
|
||||
}
|
||||
|
||||
getProxyInfo(channel) {
|
||||
|
|
@ -393,7 +354,7 @@ class PageTarget {
|
|||
this._videoRecordingInfo = undefined;
|
||||
this._screencastRecordingInfo = undefined;
|
||||
this._dialogs = new Map();
|
||||
this.forcedColors = 'none';
|
||||
this.forcedColors = 'no-override';
|
||||
this.disableCache = false;
|
||||
this.mediumOverride = '';
|
||||
this.crossProcessCookie = {
|
||||
|
|
@ -635,8 +596,7 @@ class PageTarget {
|
|||
}
|
||||
|
||||
updateForcedColorsOverride(browsingContext = undefined) {
|
||||
const isActive = this.forcedColors === 'active' || this._browserContext.forcedColors === 'active';
|
||||
(browsingContext || this._linkedBrowser.browsingContext).forcedColorsOverride = isActive ? 'active' : 'none';
|
||||
(browsingContext || this._linkedBrowser.browsingContext).forcedColorsOverride = (this.forcedColors !== 'no-override' ? this.forcedColors : this._browserContext.forcedColors) || 'no-override';
|
||||
}
|
||||
|
||||
async setInterceptFileChooserDialog(enabled) {
|
||||
|
|
@ -859,8 +819,8 @@ function fromProtocolReducedMotion(reducedMotion) {
|
|||
function fromProtocolForcedColors(forcedColors) {
|
||||
if (forcedColors === 'active' || forcedColors === 'none')
|
||||
return forcedColors;
|
||||
if (!forcedColors)
|
||||
return 'none';
|
||||
if (forcedColors === null)
|
||||
return undefined;
|
||||
throw new Error('Unknown forced colors: ' + forcedColors);
|
||||
}
|
||||
|
||||
|
|
@ -894,7 +854,7 @@ class BrowserContext {
|
|||
this.forceOffline = false;
|
||||
this.disableCache = false;
|
||||
this.colorScheme = 'none';
|
||||
this.forcedColors = 'none';
|
||||
this.forcedColors = 'no-override';
|
||||
this.reducedMotion = 'none';
|
||||
this.videoRecordingOptions = undefined;
|
||||
this.crossProcessCookie = {
|
||||
|
|
@ -946,14 +906,12 @@ class BrowserContext {
|
|||
}
|
||||
this._registry._browserContextIdToBrowserContext.delete(this.browserContextId);
|
||||
this._registry._userContextIdToBrowserContext.delete(this.userContextId);
|
||||
this._registry._updateProxiesWithSameAuthCacheAndDifferentCredentials();
|
||||
}
|
||||
|
||||
setProxy(proxy) {
|
||||
// Clear AuthCache.
|
||||
Services.obs.notifyObservers(null, "net:clear-active-logins");
|
||||
this._proxy = proxy;
|
||||
this._registry._updateProxiesWithSameAuthCacheAndDifferentCredentials();
|
||||
}
|
||||
|
||||
setIgnoreHTTPSErrors(ignoreHTTPSErrors) {
|
||||
|
|
|
|||
|
|
@ -105,10 +105,7 @@ class Juggler {
|
|||
};
|
||||
|
||||
// Force create hidden window here, otherwise its creation later closes the web socket!
|
||||
// Since https://phabricator.services.mozilla.com/D219834, hiddenDOMWindow is only available on MacOS.
|
||||
if (Services.appShell.hasHiddenWindow) {
|
||||
Services.appShell.hiddenDOMWindow;
|
||||
}
|
||||
|
||||
let pipeStopped = false;
|
||||
let browserHandler;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ class FrameTree {
|
|||
Ci.nsISupportsWeakReference,
|
||||
]);
|
||||
|
||||
this._addedScrollbarsStylesheetSymbol = Symbol('_addedScrollbarsStylesheetSymbol');
|
||||
|
||||
this._wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].createInstance(Ci.nsIWorkerDebuggerManager);
|
||||
this._wdmListener = {
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerManagerListener]),
|
||||
|
|
@ -128,12 +130,24 @@ class FrameTree {
|
|||
}
|
||||
|
||||
_onDOMWindowCreated(window) {
|
||||
if (!window[this._addedScrollbarsStylesheetSymbol] && this.scrollbarsHidden) {
|
||||
const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Components.interfaces.nsIStyleSheetService);
|
||||
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
|
||||
const uri = ioService.newURI('chrome://juggler/content/content/hidden-scrollbars.css', null, null);
|
||||
const sheet = styleSheetService.preloadSheet(uri, styleSheetService.AGENT_SHEET);
|
||||
window.windowUtils.addSheet(sheet, styleSheetService.AGENT_SHEET);
|
||||
window[this._addedScrollbarsStylesheetSymbol] = true;
|
||||
}
|
||||
const frame = this.frameForDocShell(window.docShell);
|
||||
if (!frame)
|
||||
return;
|
||||
frame._onGlobalObjectCleared();
|
||||
}
|
||||
|
||||
setScrollbarsHidden(hidden) {
|
||||
this.scrollbarsHidden = hidden;
|
||||
}
|
||||
|
||||
setJavaScriptDisabled(javaScriptDisabled) {
|
||||
this._javaScriptDisabled = javaScriptDisabled;
|
||||
for (const frame of this.frames())
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class JugglerFrameChild extends JSWindowActorChild {
|
|||
|
||||
const agents = topBrowingContextToAgents.get(this.browsingContext);
|
||||
// The agents are already re-bound to a new actor.
|
||||
if (agents?.actor !== this)
|
||||
if (agents.actor !== this)
|
||||
return;
|
||||
|
||||
topBrowingContextToAgents.delete(this.browsingContext);
|
||||
|
|
|
|||
|
|
@ -120,8 +120,7 @@ class PageAgent {
|
|||
// After the dragStart event is dispatched and handled by Web,
|
||||
// it might or might not create a new drag session, depending on its preventing default.
|
||||
setTimeout(() => {
|
||||
const session = this._getCurrentDragSession();
|
||||
this._browserPage.emit('pageInputEvent', { type: 'juggler-drag-finalized', dragSessionStarted: !!session });
|
||||
this._browserPage.emit('pageInputEvent', { type: 'juggler-drag-finalized', dragSessionStarted: !!dragService.getCurrentSession() });
|
||||
}, 0);
|
||||
}
|
||||
}),
|
||||
|
|
@ -527,14 +526,8 @@ class PageAgent {
|
|||
});
|
||||
}
|
||||
|
||||
_getCurrentDragSession() {
|
||||
const frame = this._frameTree.mainFrame();
|
||||
const domWindow = frame?.domWindow();
|
||||
return domWindow ? dragService.getCurrentSession(domWindow) : undefined;
|
||||
}
|
||||
|
||||
async _dispatchDragEvent({type, x, y, modifiers}) {
|
||||
const session = this._getCurrentDragSession();
|
||||
const session = dragService.getCurrentSession();
|
||||
const dropEffect = session.dataTransfer.dropEffect;
|
||||
|
||||
if ((type === 'drop' && dropEffect !== 'none') || type === 'dragover') {
|
||||
|
|
@ -558,8 +551,9 @@ class PageAgent {
|
|||
return;
|
||||
}
|
||||
if (type === 'dragend') {
|
||||
const session = this._getCurrentDragSession();
|
||||
session?.endDragSession(true);
|
||||
const session = dragService.getCurrentSession();
|
||||
if (session)
|
||||
dragService.endDragSession(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ function initialize(browsingContext, docShell) {
|
|||
docShell.languageOverride = locale;
|
||||
},
|
||||
|
||||
scrollbarsHidden: (hidden) => {
|
||||
data.frameTree.setScrollbarsHidden(hidden);
|
||||
},
|
||||
|
||||
javaScriptDisabled: (javaScriptDisabled) => {
|
||||
data.frameTree.setJavaScriptDisabled(javaScriptDisabled);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -255,6 +255,10 @@ class BrowserHandler {
|
|||
await this._targetRegistry.browserContextForId(browserContextId).setDefaultViewport(nullToUndefined(viewport));
|
||||
}
|
||||
|
||||
async ['Browser.setScrollbarsHidden']({browserContextId, hidden}) {
|
||||
await this._targetRegistry.browserContextForId(browserContextId).applySetting('scrollbarsHidden', nullToUndefined(hidden));
|
||||
}
|
||||
|
||||
async ['Browser.setInitScripts']({browserContextId, scripts}) {
|
||||
await this._targetRegistry.browserContextForId(browserContextId).setInitScripts(scripts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,13 +256,6 @@ class PageHandler {
|
|||
return await this._contentPage.send('disposeObject', options);
|
||||
}
|
||||
|
||||
async ['Heap.collectGarbage']() {
|
||||
Services.obs.notifyObservers(null, "child-gc-request");
|
||||
Cu.forceGC();
|
||||
Services.obs.notifyObservers(null, "child-cc-request");
|
||||
Cu.forceCC();
|
||||
}
|
||||
|
||||
async ['Network.getResponseBody']({requestId}) {
|
||||
return this._pageNetwork.getResponseBody(requestId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -394,6 +394,12 @@ const Browser = {
|
|||
viewport: t.Nullable(pageTypes.Viewport),
|
||||
}
|
||||
},
|
||||
'setScrollbarsHidden': {
|
||||
params: {
|
||||
browserContextId: t.Optional(t.String),
|
||||
hidden: t.Boolean,
|
||||
}
|
||||
},
|
||||
'setInitScripts': {
|
||||
params: {
|
||||
browserContextId: t.Optional(t.String),
|
||||
|
|
@ -481,17 +487,6 @@ const Browser = {
|
|||
},
|
||||
};
|
||||
|
||||
const Heap = {
|
||||
targets: ['page'],
|
||||
types: {},
|
||||
events: {},
|
||||
methods: {
|
||||
'collectGarbage': {
|
||||
params: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Network = {
|
||||
targets: ['page'],
|
||||
types: networkTypes,
|
||||
|
|
@ -1007,7 +1002,7 @@ const Accessibility = {
|
|||
}
|
||||
|
||||
this.protocol = {
|
||||
domains: {Browser, Heap, Page, Runtime, Network, Accessibility},
|
||||
domains: {Browser, Page, Runtime, Network, Accessibility},
|
||||
};
|
||||
this.checkScheme = checkScheme;
|
||||
this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme'];
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class nsScreencastService::Session : public rtc::VideoSinkInterface<webrtc::Vide
|
|||
capability.height = 960;
|
||||
capability.maxFPS = ScreencastEncoder::fps;
|
||||
capability.videoType = webrtc::VideoType::kI420;
|
||||
int error = mCaptureModule->StartCaptureCounted(capability);
|
||||
int error = mCaptureModule->StartCapture(capability);
|
||||
if (error) {
|
||||
fprintf(stderr, "StartCapture error %d\n", error);
|
||||
return false;
|
||||
|
|
@ -152,7 +152,7 @@ class nsScreencastService::Session : public rtc::VideoSinkInterface<webrtc::Vide
|
|||
mCaptureModule->DeRegisterCaptureDataCallback(this);
|
||||
else
|
||||
mCaptureModule->DeRegisterRawFrameCallback(this);
|
||||
mCaptureModule->StopCaptureCounted();
|
||||
mCaptureModule->StopCapture();
|
||||
if (mEncoder) {
|
||||
mEncoder->finish([this, protect = RefPtr{this}] {
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -100,11 +100,6 @@ pref("extensions.formautofill.addresses.supported", "off");
|
|||
// firefox behavior with other browser defaults.
|
||||
pref("security.enterprise_roots.enabled", true);
|
||||
|
||||
// There's a security features warning that might be shown on certain Linux distributions & configurations:
|
||||
// https://support.mozilla.org/en-US/kb/install-firefox-linux#w_security-features-warning
|
||||
// This notification should never be shown in automation scenarios.
|
||||
pref("security.sandbox.warn_unprivileged_namespaces", false);
|
||||
|
||||
// Avoid stalling on shutdown, after "xpcom-will-shutdown" phase.
|
||||
// This at least happens when shutting down soon after launching.
|
||||
// See AppShutdown.cpp for more details on shutdown phases.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
REMOTE_URL="https://github.com/WebKit/WebKit.git"
|
||||
BASE_BRANCH="main"
|
||||
BASE_REVISION="76c95d6131edd36775a5eac01e297926fc974be8"
|
||||
BASE_REVISION="a47deb713746fa2f228e8450a52ed0ecafc5309d"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
#import <WebKit/WKUserContentControllerPrivate.h>
|
||||
#import <WebKit/WKWebViewConfigurationPrivate.h>
|
||||
#import <WebKit/WKWebViewPrivate.h>
|
||||
#import <WebKit/WKWebpagePreferencesPrivate.h>
|
||||
#import <WebKit/WKWebsiteDataStorePrivate.h>
|
||||
#import <WebKit/WebNSURLExtras.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
|
@ -98,7 +97,7 @@ const NSActivityOptions ActivityOptions =
|
|||
|
||||
for (NSString *argument in subArray) {
|
||||
if (![argument hasPrefix:@"--"])
|
||||
_initialURL = [argument copy];
|
||||
_initialURL = argument;
|
||||
if ([argument hasPrefix:@"--user-data-dir="]) {
|
||||
NSRange range = NSMakeRange(16, [argument length] - 16);
|
||||
_userDataDir = [[argument substringWithRange:range] copy];
|
||||
|
|
@ -231,7 +230,7 @@ const NSActivityOptions ActivityOptions =
|
|||
configuration = [[WKWebViewConfiguration alloc] init];
|
||||
configuration.websiteDataStore = [self persistentDataStore];
|
||||
configuration._controlledByAutomation = true;
|
||||
configuration.preferences.elementFullscreenEnabled = YES;
|
||||
configuration.preferences._fullScreenEnabled = YES;
|
||||
configuration.preferences._developerExtrasEnabled = YES;
|
||||
configuration.preferences._mediaDevicesEnabled = YES;
|
||||
configuration.preferences._mockCaptureDevicesEnabled = YES;
|
||||
|
|
@ -241,8 +240,6 @@ const NSActivityOptions ActivityOptions =
|
|||
configuration.preferences._hiddenPageDOMTimerThrottlingAutoIncreases = NO;
|
||||
configuration.preferences._pageVisibilityBasedProcessSuppressionEnabled = NO;
|
||||
configuration.preferences._domTimersThrottlingEnabled = NO;
|
||||
// Do not auto play audio and video with sound.
|
||||
configuration.defaultWebpagePreferences._autoplayPolicy = _WKWebsiteAutoplayPolicyAllowWithoutSound;
|
||||
_WKProcessPoolConfiguration *processConfiguration = [[[_WKProcessPoolConfiguration alloc] init] autorelease];
|
||||
processConfiguration.forceOverlayScrollbars = YES;
|
||||
configuration.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease];
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -70,7 +70,6 @@ For example, you can use [`AxeBuilder.include()`](https://github.com/dequelabs/a
|
|||
`AxeBuilder.analyze()` will scan the page *in its current state* when you call it. To scan parts of a page that are revealed based on UI interactions, use [Locators](./locators.md) to interact with the page before invoking `analyze()`:
|
||||
|
||||
```java
|
||||
public class HomepageTests {
|
||||
@Test
|
||||
void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception {
|
||||
page.navigate("https://your-site.com/");
|
||||
|
|
@ -88,7 +87,6 @@ public class HomepageTests {
|
|||
|
||||
assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Scanning for WCAG violations
|
||||
|
|
@ -160,7 +158,6 @@ This approach avoids the downsides of using `AxeBuilder.exclude()` at the cost o
|
|||
Here is an example of using fingerprints based on only rule IDs and "target" selectors pointing to each violation:
|
||||
|
||||
```java
|
||||
public class HomepageTests {
|
||||
@Test
|
||||
shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception {
|
||||
page.navigate("https://your-site.com/");
|
||||
|
|
@ -194,7 +191,6 @@ public class HomepageTests {
|
|||
)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using a test fixture for common axe configuration
|
||||
|
|
@ -214,8 +210,8 @@ This example fixture creates an `AxeBuilder` object which is pre-configured with
|
|||
class AxeTestFixtures extends TestFixtures {
|
||||
AxeBuilder makeAxeBuilder() {
|
||||
return new AxeBuilder(page)
|
||||
.withTags(new String[]{"wcag2a", "wcag2aa", "wcag21a", "wcag21aa"})
|
||||
.exclude("#commonly-reused-element-with-known-issue");
|
||||
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
||||
.exclude('#commonly-reused-element-with-known-issue');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -233,7 +229,7 @@ public class HomepageTests extends AxeTestFixtures {
|
|||
AxeResults accessibilityScanResults = makeAxeBuilder()
|
||||
// Automatically uses the shared AxeBuilder configuration,
|
||||
// but supports additional test-specific configuration too
|
||||
.include("#specific-element-under-test")
|
||||
.include('#specific-element-under-test')
|
||||
.analyze();
|
||||
|
||||
assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
|
||||
|
|
|
|||
|
|
@ -93,20 +93,11 @@ Element is considered stable when it has maintained the same bounding box for at
|
|||
|
||||
## Enabled
|
||||
|
||||
Element is considered enabled when it is **not disabled**.
|
||||
|
||||
Element is **disabled** when:
|
||||
- it is a `<button>`, `<select>`, `<input>`, `<textarea>`, `<option>` or `<optgroup>` with a `[disabled]` attribute;
|
||||
- it is a `<button>`, `<select>`, `<input>`, `<textarea>`, `<option>` or `<optgroup>` that is a part of a `<fieldset>` with a `[disabled]` attribute;
|
||||
- it is a descendant of an element with `[aria-disabled=true]` attribute.
|
||||
Element is considered enabled unless it is a `<button>`, `<select>`, `<input>` or `<textarea>` with a `disabled` property.
|
||||
|
||||
## Editable
|
||||
|
||||
Element is considered editable when it is [enabled] and is **not readonly**.
|
||||
|
||||
Element is **readonly** when:
|
||||
- it is a `<select>`, `<input>` or `<textarea>` with a `[readonly]` attribute;
|
||||
- it has an `[aria-readonly=true]` attribute and an aria role that [supports it](https://w3c.github.io/aria/#aria-readonly).
|
||||
Element is considered editable when it is [enabled] and does not have `readonly` property set.
|
||||
|
||||
## Receives Events
|
||||
|
||||
|
|
|
|||
|
|
@ -194,7 +194,6 @@ public class TestGitHubAPI {
|
|||
These tests assume that repository exists. You probably want to create a new one before running tests and delete it afterwards. Use `@BeforeAll` and `@AfterAll` hooks for that.
|
||||
|
||||
```java
|
||||
public class TestGitHubAPI {
|
||||
// ...
|
||||
|
||||
void createTestRepository() {
|
||||
|
|
@ -224,7 +223,6 @@ public class TestGitHubAPI {
|
|||
disposeAPIRequestContext();
|
||||
closePlaywright();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Complete test example
|
||||
|
|
@ -383,7 +381,6 @@ The following test creates a new issue via API and then navigates to the list of
|
|||
project to check that it appears at the top of the list. The check is performed using [LocatorAssertions].
|
||||
|
||||
```java
|
||||
public class TestGitHubAPI {
|
||||
@Test
|
||||
void lastCreatedIssueShouldBeFirstInTheList() {
|
||||
Map<String, String> data = new HashMap<>();
|
||||
|
|
@ -397,7 +394,6 @@ public class TestGitHubAPI {
|
|||
Locator firstIssue = page.locator("a[data-hovercard-type='issue']").first();
|
||||
assertThat(firstIssue).hasText("[Feature] request 1");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Check the server state after running user actions
|
||||
|
|
@ -406,7 +402,6 @@ The following test creates a new issue via user interface in the browser and the
|
|||
it was created:
|
||||
|
||||
```java
|
||||
public class TestGitHubAPI {
|
||||
@Test
|
||||
void lastCreatedIssueShouldBeOnTheServer() {
|
||||
page.navigate("https://github.com/" + USER + "/" + REPO + "/issues");
|
||||
|
|
@ -420,7 +415,6 @@ public class TestGitHubAPI {
|
|||
assertThat(newIssue).isOK();
|
||||
assertTrue(newIssue.text().contains("Bug report 1"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reuse authentication state
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ Launches Chrome browser on the device, and returns its persistent context.
|
|||
|
||||
### option: AndroidDevice.launchBrowser.pkg
|
||||
* since: v1.9
|
||||
- `pkg` <[string]>
|
||||
- `command` <[string]>
|
||||
|
||||
Optional package name to launch instead of default Chrome for Android.
|
||||
|
||||
|
|
@ -177,9 +177,7 @@ Launches a process in the shell on the device and returns a socket to communicat
|
|||
|
||||
### param: AndroidDevice.open.command
|
||||
* since: v1.9
|
||||
- `command` <[string]>
|
||||
|
||||
Shell command to execute.
|
||||
- `command` <[string]> Shell command to execute.
|
||||
|
||||
## async method: AndroidDevice.pinchClose
|
||||
* since: v1.9
|
||||
|
|
@ -447,7 +445,7 @@ Either a predicate that receives an event or an options object. Optional.
|
|||
* since: v1.9
|
||||
- returns: <[AndroidWebView]>
|
||||
|
||||
This method waits until [AndroidWebView] matching the [`param: selector`] is opened and returns it. If there is already an open [AndroidWebView] matching the [`param: selector`], returns immediately.
|
||||
This method waits until [AndroidWebView] matching the [`option: selector`] is opened and returns it. If there is already an open [AndroidWebView] matching the [`option: selector`], returns immediately.
|
||||
|
||||
### param: AndroidDevice.webView.selector
|
||||
* since: v1.9
|
||||
|
|
|
|||
|
|
@ -21,13 +21,6 @@ Creates new instances of [APIRequestContext].
|
|||
### option: APIRequest.newContext.extraHTTPHeaders = %%-context-option-extrahttpheaders-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequest.newContext.failOnStatusCode
|
||||
* since: v1.51
|
||||
- `failOnStatusCode` <[boolean]>
|
||||
|
||||
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
|
||||
for all status codes.
|
||||
|
||||
### option: APIRequest.newContext.httpCredentials = %%-context-option-httpcredentials-%%
|
||||
* since: v1.16
|
||||
|
||||
|
|
@ -71,7 +64,6 @@ Methods like [`method: APIRequestContext.get`] take the base URL into considerat
|
|||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `indexedDB` ?<[Array]<[unknown]>> indexedDB to set for context
|
||||
|
||||
Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||
obtained via [`method: BrowserContext.storageState`] or [`method: APIRequestContext.storageState`]. Either a path to the
|
||||
|
|
|
|||
|
|
@ -159,10 +159,7 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.delete.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.17
|
||||
|
||||
### option: APIRequestContext.delete.form = %%-js-fetch-option-form-%%
|
||||
* since: v1.17
|
||||
|
||||
### option: APIRequestContext.delete.form = %%-python-fetch-option-form-%%
|
||||
### option: APIRequestContext.delete.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.17
|
||||
|
||||
### option: APIRequestContext.delete.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -247,7 +244,7 @@ var data = new Dictionary<string, object>() {
|
|||
await Request.FetchAsync("https://example.com/api/createBook", new() { Method = "post", DataObject = data });
|
||||
```
|
||||
|
||||
The common way to send file(s) in the body of a request is to upload them as form fields with `multipart/form-data` encoding, by specifiying the `multipart` parameter:
|
||||
The common way to send file(s) in the body of a request is to upload them as form fields with `multipart/form-data` encoding. Use [FormData] to construct request body and pass it to the request as [`option: multipart`] parameter:
|
||||
|
||||
```js
|
||||
const form = new FormData();
|
||||
|
|
@ -300,7 +297,6 @@ multipart.Set("fileField", file);
|
|||
await Request.FetchAsync("https://example.com/api/uploadScript", new() { Method = "post", Multipart = multipart });
|
||||
```
|
||||
|
||||
|
||||
### param: APIRequestContext.fetch.urlOrRequest
|
||||
* since: v1.16
|
||||
- `urlOrRequest` <[string]|[Request]>
|
||||
|
|
@ -336,10 +332,7 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
|
|||
### option: APIRequestContext.fetch.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.fetch.form = %%-js-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.fetch.form = %%-python-fetch-option-form-%%
|
||||
### option: APIRequestContext.fetch.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.fetch.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -449,10 +442,7 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
|
|||
### option: APIRequestContext.get.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.get.form = %%-js-fetch-option-form-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.get.form = %%-python-fetch-option-form-%%
|
||||
### option: APIRequestContext.get.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.get.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -514,10 +504,7 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.head.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.head.form = %%-python-fetch-option-form-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.head.form = %%-js-fetch-option-form-%%
|
||||
### option: APIRequestContext.head.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.26
|
||||
|
||||
### option: APIRequestContext.head.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -579,10 +566,7 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.patch.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.patch.form = %%-js-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.patch.form = %%-python-fetch-option-form-%%
|
||||
### option: APIRequestContext.patch.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.patch.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -765,10 +749,7 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
|
|||
### option: APIRequestContext.post.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.post.form = %%-js-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.post.form = %%-python-fetch-option-form-%%
|
||||
### option: APIRequestContext.post.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.post.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -830,10 +811,7 @@ context cookies from the response. The method will automatically follow redirect
|
|||
### option: APIRequestContext.put.data = %%-js-python-csharp-fetch-option-data-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.put.form = %%-python-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.put.form = %%-js-fetch-option-form-%%
|
||||
### option: APIRequestContext.put.form = %%-js-python-fetch-option-form-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.put.form = %%-csharp-fetch-option-form-%%
|
||||
|
|
@ -880,7 +858,6 @@ context cookies from the response. The method will automatically follow redirect
|
|||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `indexedDB` <[Array]<[unknown]>>
|
||||
|
||||
Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to the constructor.
|
||||
|
||||
|
|
@ -891,9 +868,3 @@ Returns storage state for this request context, contains current cookies and loc
|
|||
|
||||
### option: APIRequestContext.storageState.path = %%-storagestate-option-path-%%
|
||||
* since: v1.16
|
||||
|
||||
### option: APIRequestContext.storageState.indexedDB
|
||||
* since: v1.51
|
||||
- `indexedDB` ?<boolean>
|
||||
|
||||
Set to `true` to include IndexedDB in the storage state snapshot.
|
||||
|
|
|
|||
|
|
@ -14,15 +14,15 @@ test('navigates to login', async ({ page }) => {
|
|||
```
|
||||
|
||||
```java
|
||||
// ...
|
||||
...
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
|
||||
public class TestPage {
|
||||
// ...
|
||||
...
|
||||
@Test
|
||||
void navigatesToLoginPage() {
|
||||
// ...
|
||||
APIResponse response = page.request().get("https://playwright.dev");
|
||||
...
|
||||
APIResponse response = page.request().get('https://playwright.dev');
|
||||
assertThat(response).isOK();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ import com.microsoft.playwright.*;
|
|||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
BrowserType firefox = playwright.firefox();
|
||||
BrowserType firefox = playwright.firefox()
|
||||
Browser browser = firefox.launch();
|
||||
Page page = browser.newPage();
|
||||
page.navigate("https://example.com");
|
||||
page.navigate('https://example.com');
|
||||
browser.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ In case this browser is connected to, clears all created contexts belonging to t
|
|||
browser server.
|
||||
|
||||
:::note
|
||||
This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call [`method: BrowserContext.close`] on any [BrowserContext] instances you explicitly created earlier using [`method: Browser.newContext`] **before** calling [`method: Browser.close`].
|
||||
This is similar to force quitting the browser. Therefore, you should call [`method: BrowserContext.close`] on any [BrowserContext]'s you explicitly created earlier with [`method: Browser.newContext`] **before** calling [`method: Browser.close`].
|
||||
:::
|
||||
|
||||
The [Browser] object itself is considered to be disposed and cannot be used anymore.
|
||||
|
|
@ -202,7 +202,7 @@ Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
|
|||
BrowserContext context = browser.newContext();
|
||||
// Create a new page in a pristine context.
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://example.com");
|
||||
page.navigate('https://example.com');
|
||||
|
||||
// Graceful close up everything
|
||||
context.close();
|
||||
|
|
@ -331,7 +331,7 @@ await browser.stopTracing();
|
|||
```java
|
||||
browser.startTracing(page, new Browser.StartTracingOptions()
|
||||
.setPath(Paths.get("trace.json")));
|
||||
page.navigate("https://www.google.com");
|
||||
page.goto('https://www.google.com');
|
||||
browser.stopTracing();
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -655,7 +655,7 @@ import com.microsoft.playwright.*;
|
|||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
BrowserType webkit = playwright.webkit();
|
||||
BrowserType webkit = playwright.webkit()
|
||||
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
|
||||
BrowserContext context = browser.newContext();
|
||||
context.exposeBinding("pageURL", (source, args) -> source.page().url());
|
||||
|
|
@ -813,9 +813,8 @@ import java.util.Base64;
|
|||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
BrowserType webkit = playwright.webkit();
|
||||
BrowserType webkit = playwright.webkit()
|
||||
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
|
||||
BrowserContext context = browser.newContext();
|
||||
context.exposeFunction("sha256", args -> {
|
||||
String text = (String) args[0];
|
||||
MessageDigest crypto;
|
||||
|
|
@ -963,14 +962,9 @@ specified.
|
|||
* since: v1.8
|
||||
- `permissions` <[Array]<[string]>>
|
||||
|
||||
A list of permissions to grant.
|
||||
|
||||
:::danger
|
||||
Supported permissions differ between browsers, and even between different versions of the same browser. Any permission may stop working after an update.
|
||||
:::
|
||||
|
||||
Here are some permissions that may be supported by some browsers:
|
||||
A permission or an array of permissions to grant. Permissions can be one of the following values:
|
||||
* `'accelerometer'`
|
||||
* `'accessibility-events'`
|
||||
* `'ambient-light-sensor'`
|
||||
* `'background-sync'`
|
||||
* `'camera'`
|
||||
|
|
@ -1204,7 +1198,7 @@ Enabling routing disables http cache.
|
|||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: BrowserContext.route.handler
|
||||
|
|
@ -1272,99 +1266,6 @@ When set to `minimal`, only record information necessary for routing from HAR. T
|
|||
|
||||
Optional setting to control resource content management. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file.
|
||||
|
||||
|
||||
## async method: BrowserContext.routeWebSocket
|
||||
* since: v1.48
|
||||
|
||||
This method allows to modify websocket connections that are made by any page in the browser context.
|
||||
|
||||
Note that only `WebSocket`s created after this method was called will be routed. It is recommended to call this method before creating any pages.
|
||||
|
||||
**Usage**
|
||||
|
||||
Below is an example of a simple handler that blocks some websocket messages.
|
||||
See [WebSocketRoute] for more details and examples.
|
||||
|
||||
```js
|
||||
await context.routeWebSocket('/ws', async ws => {
|
||||
ws.routeSend(message => {
|
||||
if (message === 'to-be-blocked')
|
||||
return;
|
||||
ws.send(message);
|
||||
});
|
||||
await ws.connect();
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
context.routeWebSocket("/ws", ws -> {
|
||||
ws.routeSend(message -> {
|
||||
if ("to-be-blocked".equals(message))
|
||||
return;
|
||||
ws.send(message);
|
||||
});
|
||||
ws.connect();
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "to-be-blocked":
|
||||
return
|
||||
ws.send(message)
|
||||
|
||||
async def handler(ws: WebSocketRoute):
|
||||
ws.route_send(lambda message: message_handler(ws, message))
|
||||
await ws.connect()
|
||||
|
||||
await context.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "to-be-blocked":
|
||||
return
|
||||
ws.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
ws.route_send(lambda message: message_handler(ws, message))
|
||||
ws.connect()
|
||||
|
||||
context.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```csharp
|
||||
await context.RouteWebSocketAsync("/ws", async ws => {
|
||||
ws.RouteSend(message => {
|
||||
if (message == "to-be-blocked")
|
||||
return;
|
||||
ws.Send(message);
|
||||
});
|
||||
await ws.ConnectAsync();
|
||||
});
|
||||
```
|
||||
|
||||
### param: BrowserContext.routeWebSocket.url
|
||||
* since: v1.48
|
||||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option.
|
||||
|
||||
### param: BrowserContext.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
* langs: js, python
|
||||
- `handler` <[function]\([WebSocketRoute]\): [Promise<any>|any]>
|
||||
|
||||
Handler function to route the WebSocket.
|
||||
|
||||
### param: BrowserContext.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
* langs: csharp, java
|
||||
- `handler` <[function]\([WebSocketRoute]\)>
|
||||
|
||||
Handler function to route the WebSocket.
|
||||
|
||||
|
||||
## method: BrowserContext.serviceWorkers
|
||||
* since: v1.11
|
||||
* langs: js, python
|
||||
|
|
@ -1412,7 +1313,7 @@ This setting will change the default maximum time for all the methods accepting
|
|||
* since: v1.8
|
||||
- `timeout` <[float]>
|
||||
|
||||
Maximum time in milliseconds. Pass `0` to disable timeout.
|
||||
Maximum time in milliseconds
|
||||
|
||||
## async method: BrowserContext.setExtraHTTPHeaders
|
||||
* since: v1.8
|
||||
|
|
@ -1511,9 +1412,8 @@ Whether to emulate network being offline for the browser context.
|
|||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `indexedDB` <[Array]<[unknown]>>
|
||||
|
||||
Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
|
||||
Returns storage state for this browser context, contains current cookies and local storage snapshot.
|
||||
|
||||
## async method: BrowserContext.storageState
|
||||
* since: v1.8
|
||||
|
|
@ -1523,17 +1423,6 @@ Returns storage state for this browser context, contains current cookies, local
|
|||
### option: BrowserContext.storageState.path = %%-storagestate-option-path-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: BrowserContext.storageState.indexedDB
|
||||
* since: v1.51
|
||||
- `indexedDB` ?<boolean>
|
||||
|
||||
Set to `true` to include IndexedDB in the storage state snapshot.
|
||||
If your application uses IndexedDB to store authentication tokens, like Firebase Authentication, enable this.
|
||||
|
||||
:::note
|
||||
IndexedDBs with typed arrays are currently not supported.
|
||||
:::
|
||||
|
||||
## property: BrowserContext.tracing
|
||||
* since: v1.12
|
||||
- type: <[Tracing]>
|
||||
|
|
|
|||
|
|
@ -89,17 +89,13 @@ class BrowserTypeExamples
|
|||
* since: v1.8
|
||||
- returns: <[Browser]>
|
||||
|
||||
This method attaches Playwright to an existing browser instance created via `BrowserType.launchServer` in Node.js.
|
||||
|
||||
:::note
|
||||
The major and minor version of the Playwright instance that connects needs to match the version of Playwright that launches the browser (1.2.3 → is compatible with 1.2.x).
|
||||
:::
|
||||
This method attaches Playwright to an existing browser instance. When connecting to another browser launched via `BrowserType.launchServer` in Node.js, the major and minor version needs to match the client version (1.2.3 → is compatible with 1.2.x).
|
||||
|
||||
### param: BrowserType.connect.wsEndpoint
|
||||
* since: v1.10
|
||||
- `wsEndpoint` <[string]>
|
||||
|
||||
A Playwright browser websocket endpoint to connect to. You obtain this endpoint via `BrowserServer.wsEndpoint`.
|
||||
A browser websocket endpoint to connect to.
|
||||
|
||||
### option: BrowserType.connect.headers
|
||||
* since: v1.11
|
||||
|
|
@ -156,10 +152,6 @@ The default browser context is accessible via [`method: Browser.contexts`].
|
|||
Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
|
||||
:::
|
||||
|
||||
:::note
|
||||
This connection is significantly lower fidelity than the Playwright protocol connection via [`method: BrowserType.connect`]. If you are experiencing issues or attempting to use advanced functionality, you probably want to use [`method: BrowserType.connect`].
|
||||
:::
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
|
|
|
|||
|
|
@ -161,41 +161,6 @@ await page.Clock.PauseAtAsync(DateTime.Parse("2020-02-02"));
|
|||
await page.Clock.PauseAtAsync("2020-02-02");
|
||||
```
|
||||
|
||||
For best results, install the clock before navigating the page and set it to a time slightly before the intended test time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the page has fully loaded, you can safely use [`method: Clock.pauseAt`] to pause the clock.
|
||||
|
||||
```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-12-10T08:00:00') });
|
||||
await page.goto('http://localhost:3333');
|
||||
await page.clock.pauseAt(new Date('2024-12-10T10:00:00'));
|
||||
```
|
||||
|
||||
```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, 12, 10, 8, 0, 0))
|
||||
await page.goto("http://localhost:3333")
|
||||
await page.clock.pause_at(datetime.datetime(2024, 12, 10, 10, 0, 0))
|
||||
```
|
||||
|
||||
```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, 12, 10, 8, 0, 0))
|
||||
page.goto("http://localhost:3333")
|
||||
page.clock.pause_at(datetime.datetime(2024, 12, 10, 10, 0, 0))
|
||||
```
|
||||
|
||||
```java
|
||||
// Initialize clock with some time before the test time and let the page load
|
||||
// naturally. `Date.now` will progress as the timers fire.
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
|
||||
page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
|
||||
page.navigate("http://localhost:3333");
|
||||
page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
|
||||
```
|
||||
|
||||
### param: Clock.pauseAt.time
|
||||
* langs: js, java
|
||||
* since: v1.45
|
||||
|
|
@ -228,8 +193,6 @@ Resumes timers. Once this method is called, time resumes flowing, timers are fir
|
|||
Makes `Date.now` and `new Date()` return fixed fake time at all times,
|
||||
keeps all the timers running.
|
||||
|
||||
Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios, use [`method: Clock.install`] instead. Read docs on [clock emulation](../clock.md) to learn more.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
|
|
@ -286,7 +249,7 @@ Time to be set.
|
|||
## async method: Clock.setSystemTime
|
||||
* since: v1.45
|
||||
|
||||
Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example switching from summer to winter time, or changing time zones.
|
||||
Sets current system time but does not trigger any timers.
|
||||
|
||||
**Usage**
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ ConsoleMessage msg = page.waitForConsoleMessage(() -> {
|
|||
});
|
||||
|
||||
// Deconstruct console.log arguments
|
||||
msg.args().get(0).jsonValue(); // hello
|
||||
msg.args().get(1).jsonValue(); // 42
|
||||
msg.args().get(0).jsonValue() // hello
|
||||
msg.args().get(1).jsonValue() // 42
|
||||
```
|
||||
|
||||
```python async
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ The [FormData] is used create form data that is sent via [APIRequestContext].
|
|||
|
||||
```java
|
||||
import com.microsoft.playwright.options.FormData;
|
||||
// ...
|
||||
...
|
||||
FormData form = FormData.create()
|
||||
.set("firstName", "John")
|
||||
.set("lastName", "Doe")
|
||||
|
|
@ -28,7 +28,7 @@ the new value onto the end of the existing set of values.
|
|||
|
||||
```java
|
||||
import com.microsoft.playwright.options.FormData;
|
||||
// ...
|
||||
...
|
||||
FormData form = FormData.create()
|
||||
// Only name and value are set.
|
||||
.append("firstName", "John")
|
||||
|
|
@ -100,7 +100,7 @@ Sets a field on the form. File values can be passed either as `Path` or as `File
|
|||
|
||||
```java
|
||||
import com.microsoft.playwright.options.FormData;
|
||||
// ...
|
||||
...
|
||||
FormData form = FormData.create()
|
||||
// Only name and value are set.
|
||||
.set("firstName", "John")
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Frame.click.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Frame.click.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Frame.click.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Frame.content
|
||||
|
|
@ -341,7 +341,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Frame.dblclick.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Frame.dblclick.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Frame.dblclick.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Frame.dispatchEvent
|
||||
|
|
@ -1153,7 +1153,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Frame.hover.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Frame.hover.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Frame.hover.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
### option: Frame.hover.noWaitAfter = %%-input-no-wait-after-removed-%%
|
||||
|
|
@ -1304,7 +1304,7 @@ Returns whether the element is [enabled](../actionability.md#enabled).
|
|||
* discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered hidden.
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered hidden.
|
||||
|
||||
### param: Frame.isHidden.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -1322,7 +1322,7 @@ Returns whether the element is hidden, the opposite of [visible](../actionabilit
|
|||
* discouraged: Use locator-based [`method: Locator.isVisible`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered not visible.
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered not visible.
|
||||
|
||||
### param: Frame.isVisible.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -1703,7 +1703,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Frame.tap.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Frame.tap.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Frame.tap.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Frame.textContent
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
# class: FrameLocator
|
||||
* since: v1.17
|
||||
|
||||
FrameLocator represents a view to the `iframe` on the page. It captures the logic sufficient to retrieve the `iframe` and locate elements in that iframe. FrameLocator can be created with either [`method: Locator.contentFrame`], [`method: Page.frameLocator`] or [`method: Locator.frameLocator`] method.
|
||||
FrameLocator represents a view to the `iframe` on the page. It captures the logic sufficient to retrieve the `iframe` and locate elements in that iframe. FrameLocator can be created with either [`method: Page.frameLocator`] or [`method: Locator.frameLocator`] method.
|
||||
|
||||
```js
|
||||
const locator = page.locator('#my-frame').contentFrame().getByText('Submit');
|
||||
const locator = page.frameLocator('#my-frame').getByText('Submit');
|
||||
await locator.click();
|
||||
```
|
||||
|
||||
```java
|
||||
Locator locator = page.locator("#my-frame").contentFrame().getByText("Submit");
|
||||
Locator locator = page.frameLocator("#my-frame").getByText("Submit");
|
||||
locator.click();
|
||||
```
|
||||
|
||||
```python async
|
||||
locator = page.locator("#my-frame").content_frame.get_by_text("Submit")
|
||||
locator = page.frame_locator("#my-frame").get_by_text("Submit")
|
||||
await locator.click()
|
||||
```
|
||||
|
||||
```python sync
|
||||
locator = page.locator("my-frame").content_frame.get_by_text("Submit")
|
||||
locator = page.frame_locator("my-frame").get_by_text("Submit")
|
||||
locator.click()
|
||||
```
|
||||
|
||||
```csharp
|
||||
var locator = page.Locator("#my-frame").ContentFrame.GetByText("Submit");
|
||||
var locator = page.FrameLocator("#my-frame").GetByText("Submit");
|
||||
await locator.ClickAsync();
|
||||
```
|
||||
|
||||
|
|
@ -34,42 +34,42 @@ Frame locators are strict. This means that all operations on frame locators will
|
|||
|
||||
```js
|
||||
// Throws if there are several frames in DOM:
|
||||
await page.locator('.result-frame').contentFrame().getByRole('button').click();
|
||||
await page.frameLocator('.result-frame').getByRole('button').click();
|
||||
|
||||
// Works because we explicitly tell locator to pick the first frame:
|
||||
await page.locator('.result-frame').contentFrame().first().getByRole('button').click();
|
||||
await page.frameLocator('.result-frame').first().getByRole('button').click();
|
||||
```
|
||||
|
||||
```python async
|
||||
# Throws if there are several frames in DOM:
|
||||
await page.locator('.result-frame').content_frame.get_by_role('button').click()
|
||||
await page.frame_locator('.result-frame').get_by_role('button').click()
|
||||
|
||||
# Works because we explicitly tell locator to pick the first frame:
|
||||
await page.locator('.result-frame').first.content_frame.get_by_role('button').click()
|
||||
await page.frame_locator('.result-frame').first.get_by_role('button').click()
|
||||
```
|
||||
|
||||
```python sync
|
||||
# Throws if there are several frames in DOM:
|
||||
page.locator('.result-frame').content_frame.get_by_role('button').click()
|
||||
page.frame_locator('.result-frame').get_by_role('button').click()
|
||||
|
||||
# Works because we explicitly tell locator to pick the first frame:
|
||||
page.locator('.result-frame').first.content_frame.get_by_role('button').click()
|
||||
page.frame_locator('.result-frame').first.get_by_role('button').click()
|
||||
```
|
||||
|
||||
```java
|
||||
// Throws if there are several frames in DOM:
|
||||
page.locator(".result-frame").contentFrame().getByRole(AriaRole.BUTTON).click();
|
||||
page.frame_locator(".result-frame").getByRole(AriaRole.BUTTON).click();
|
||||
|
||||
// Works because we explicitly tell locator to pick the first frame:
|
||||
page.locator(".result-frame").first().contentFrame().getByRole(AriaRole.BUTTON).click();
|
||||
page.frame_locator(".result-frame").first().getByRole(AriaRole.BUTTON).click();
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Throws if there are several frames in DOM:
|
||||
await page.Locator(".result-frame").ContentFrame.GetByRole(AriaRole.Button).ClickAsync();
|
||||
await page.FrameLocator(".result-frame").GetByRole(AriaRole.Button).ClickAsync();
|
||||
|
||||
// Works because we explicitly tell locator to pick the first frame:
|
||||
await page.Locator(".result-frame").First.ContentFrame.getByRole(AriaRole.Button).ClickAsync();
|
||||
await page.FrameLocator(".result-frame").First.getByRole(AriaRole.Button).ClickAsync();
|
||||
```
|
||||
|
||||
**Converting Locator to FrameLocator**
|
||||
|
|
@ -82,7 +82,6 @@ If you have a [FrameLocator] object it can be converted to [Locator] pointing to
|
|||
|
||||
|
||||
## method: FrameLocator.first
|
||||
* deprecated: Use [`method: Locator.first`] followed by [`method: Locator.contentFrame`] instead.
|
||||
* since: v1.17
|
||||
- returns: <[FrameLocator]>
|
||||
|
||||
|
|
@ -172,7 +171,6 @@ in that iframe.
|
|||
### option: FrameLocator.getByTitle.exact = %%-locator-get-by-text-exact-%%
|
||||
|
||||
## method: FrameLocator.last
|
||||
* deprecated: Use [`method: Locator.last`] followed by [`method: Locator.contentFrame`] instead.
|
||||
* since: v1.17
|
||||
- returns: <[FrameLocator]>
|
||||
|
||||
|
|
@ -197,7 +195,6 @@ Returns locator to the last matching frame.
|
|||
* since: v1.33
|
||||
|
||||
## method: FrameLocator.nth
|
||||
* deprecated: Use [`method: Locator.nth`] followed by [`method: Locator.contentFrame`] instead.
|
||||
* since: v1.17
|
||||
- returns: <[FrameLocator]>
|
||||
|
||||
|
|
@ -220,36 +217,37 @@ For a reverse operation, use [`method: Locator.contentFrame`].
|
|||
**Usage**
|
||||
|
||||
```js
|
||||
const frameLocator = page.locator('iframe[name="embedded"]').contentFrame();
|
||||
const frameLocator = page.frameLocator('iframe[name="embedded"]');
|
||||
// ...
|
||||
const locator = frameLocator.owner();
|
||||
await expect(locator).toBeVisible();
|
||||
```
|
||||
|
||||
```java
|
||||
FrameLocator frameLocator = page.locator("iframe[name=\"embedded\"]").contentFrame();
|
||||
FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]");
|
||||
// ...
|
||||
Locator locator = frameLocator.owner();
|
||||
assertThat(locator).isVisible();
|
||||
```
|
||||
|
||||
```python async
|
||||
frame_locator = page.locator("iframe[name=\"embedded\"]").content_frame
|
||||
frame_locator = page.frame_locator("iframe[name=\"embedded\"]")
|
||||
# ...
|
||||
locator = frame_locator.owner
|
||||
await expect(locator).to_be_visible()
|
||||
```
|
||||
|
||||
```python sync
|
||||
frame_locator = page.locator("iframe[name=\"embedded\"]").content_frame
|
||||
frame_locator = page.frame_locator("iframe[name=\"embedded\"]")
|
||||
# ...
|
||||
locator = frame_locator.owner
|
||||
expect(locator).to_be_visible()
|
||||
```
|
||||
|
||||
```csharp
|
||||
var frameLocator = Page.Locator("iframe[name=\"embedded\"]").ContentFrame;
|
||||
var frameLocator = Page.FrameLocator("iframe[name=\"embedded\"]");
|
||||
// ...
|
||||
var locator = frameLocator.Owner;
|
||||
await Expect(locator).ToBeVisibleAsync();
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -104,23 +104,38 @@ await page.Keyboard.PressAsync("Shift+A");
|
|||
An example to trigger select-all with the keyboard
|
||||
|
||||
```js
|
||||
await page.keyboard.press('ControlOrMeta+A');
|
||||
// on Windows and Linux
|
||||
await page.keyboard.press('Control+A');
|
||||
// on macOS
|
||||
await page.keyboard.press('Meta+A');
|
||||
```
|
||||
|
||||
```java
|
||||
page.keyboard().press("ControlOrMeta+A");
|
||||
// on Windows and Linux
|
||||
page.keyboard().press("Control+A");
|
||||
// on macOS
|
||||
page.keyboard().press("Meta+A");
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.keyboard.press("ControlOrMeta+A")
|
||||
# on windows and linux
|
||||
await page.keyboard.press("Control+A")
|
||||
# on mac_os
|
||||
await page.keyboard.press("Meta+A")
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.keyboard.press("ControlOrMeta+A")
|
||||
# on windows and linux
|
||||
page.keyboard.press("Control+A")
|
||||
# on mac_os
|
||||
page.keyboard.press("Meta+A")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.Keyboard.PressAsync("ControlOrMeta+A");
|
||||
// on Windows and Linux
|
||||
await page.Keyboard.PressAsync("Control+A");
|
||||
// on macOS
|
||||
await page.Keyboard.PressAsync("Meta+A");
|
||||
```
|
||||
|
||||
## async method: Keyboard.down
|
||||
|
|
@ -242,7 +257,7 @@ await browser.close();
|
|||
Page page = browser.newPage();
|
||||
page.navigate("https://keycode.info");
|
||||
page.keyboard().press("A");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")));
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
|
||||
page.keyboard().press("ArrowLeft");
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
|
||||
page.keyboard().press("Shift+O");
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ for li in page.get_by_role('listitem').all():
|
|||
```
|
||||
|
||||
```java
|
||||
for (Locator li : page.getByRole("listitem").all())
|
||||
for (Locator li : page.getByRole('listitem').all())
|
||||
li.click();
|
||||
```
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ foreach (var li in await page.GetByRole("listitem").AllAsync())
|
|||
Returns an array of `node.innerText` values for all matching nodes.
|
||||
|
||||
:::warning[Asserting text]
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: LocatorAssertions.toHaveText.useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
:::
|
||||
|
||||
**Usage**
|
||||
|
|
@ -150,67 +150,6 @@ var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
|
|||
|
||||
Additional locator to match.
|
||||
|
||||
## async method: Locator.ariaSnapshot
|
||||
* since: v1.49
|
||||
- returns: <[string]>
|
||||
|
||||
Captures the aria snapshot of the given element.
|
||||
Read more about [aria snapshots](../aria-snapshots.md) and [`method: LocatorAssertions.toMatchAriaSnapshot`] for the corresponding assertion.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
await page.getByRole('link').ariaSnapshot();
|
||||
```
|
||||
|
||||
```java
|
||||
page.getByRole(AriaRole.LINK).ariaSnapshot();
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.get_by_role("link").aria_snapshot()
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.get_by_role("link").aria_snapshot()
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.GetByRole(AriaRole.Link).AriaSnapshotAsync();
|
||||
```
|
||||
|
||||
**Details**
|
||||
|
||||
This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of the element and its children.
|
||||
The snapshot can be used to assert the state of the element in the test, or to compare it to state in the future.
|
||||
|
||||
The ARIA snapshot is represented using [YAML](https://yaml.org/spec/1.2.2/) markup language:
|
||||
* The keys of the objects are the roles and optional accessible names of the elements.
|
||||
* The values are either text content or an array of child elements.
|
||||
* Generic static text can be represented with the `text` key.
|
||||
|
||||
Below is the HTML markup and the respective ARIA snapshot:
|
||||
|
||||
```html
|
||||
<ul aria-label="Links">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
<ul>
|
||||
```
|
||||
|
||||
```yml
|
||||
- list "Links":
|
||||
- listitem:
|
||||
- link "Home"
|
||||
- listitem:
|
||||
- link "About"
|
||||
```
|
||||
|
||||
### option: Locator.ariaSnapshot.timeout = %%-input-timeout-%%
|
||||
* since: v1.49
|
||||
|
||||
### option: Locator.ariaSnapshot.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.49
|
||||
|
||||
## async method: Locator.blur
|
||||
* since: v1.28
|
||||
|
|
@ -494,7 +433,7 @@ await page.Locator("canvas").ClickAsync(new() {
|
|||
### option: Locator.click.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.14
|
||||
|
||||
### option: Locator.click.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Locator.click.trial = %%-input-trial-%%
|
||||
* since: v1.14
|
||||
|
||||
## async method: Locator.count
|
||||
|
|
@ -577,7 +516,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Locator.dblclick.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.14
|
||||
|
||||
### option: Locator.dblclick.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Locator.dblclick.trial = %%-input-trial-%%
|
||||
* since: v1.14
|
||||
|
||||
## async method: Locator.dispatchEvent
|
||||
|
|
@ -633,11 +572,13 @@ properties:
|
|||
You can also specify [JSHandle] as the property value if you want live objects to be passed into the event:
|
||||
|
||||
```js
|
||||
// Note you can only create DataTransfer in Chromium and Firefox
|
||||
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
|
||||
await locator.dispatchEvent('dragstart', { dataTransfer });
|
||||
```
|
||||
|
||||
```java
|
||||
// Note you can only create DataTransfer in Chromium and Firefox
|
||||
JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
|
||||
Map<String, Object> arg = new HashMap<>();
|
||||
arg.put("dataTransfer", dataTransfer);
|
||||
|
|
@ -645,11 +586,13 @@ locator.dispatchEvent("dragstart", arg);
|
|||
```
|
||||
|
||||
```python async
|
||||
# note you can only create data_transfer in chromium and firefox
|
||||
data_transfer = await page.evaluate_handle("new DataTransfer()")
|
||||
await locator.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer})
|
||||
```
|
||||
|
||||
```python sync
|
||||
# note you can only create data_transfer in chromium and firefox
|
||||
data_transfer = page.evaluate_handle("new DataTransfer()")
|
||||
locator.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer})
|
||||
```
|
||||
|
|
@ -864,6 +807,31 @@ If [`param: expression`] throws or rejects, this method throws.
|
|||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
const tweets = page.locator('.tweet .retweets');
|
||||
expect(await tweets.evaluate(node => node.innerText)).toBe('10 retweets');
|
||||
```
|
||||
|
||||
```java
|
||||
Locator tweets = page.locator(".tweet .retweets");
|
||||
assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
|
||||
```
|
||||
|
||||
```python async
|
||||
tweets = page.locator(".tweet .retweets")
|
||||
assert await tweets.evaluate("node => node.innerText") == "10 retweets"
|
||||
```
|
||||
|
||||
```python sync
|
||||
tweets = page.locator(".tweet .retweets")
|
||||
assert tweets.evaluate("node => node.innerText") == "10 retweets"
|
||||
```
|
||||
|
||||
```csharp
|
||||
var tweets = page.Locator(".tweet .retweets");
|
||||
Assert.AreEqual("10 retweets", await tweets.EvaluateAsync("node => node.innerText"));
|
||||
```
|
||||
|
||||
### param: Locator.evaluate.expression = %%-evaluate-expression-%%
|
||||
* since: v1.14
|
||||
|
||||
|
|
@ -1090,9 +1058,6 @@ await rowLocator
|
|||
### option: Locator.filter.hasNotText = %%-locator-option-has-not-text-%%
|
||||
* since: v1.33
|
||||
|
||||
### option: Locator.filter.visible = %%-locator-option-visible-%%
|
||||
* since: v1.51
|
||||
|
||||
## method: Locator.first
|
||||
* since: v1.14
|
||||
- returns: <[Locator]>
|
||||
|
|
@ -1301,7 +1266,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Locator.hover.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.14
|
||||
|
||||
### option: Locator.hover.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Locator.hover.trial = %%-input-trial-%%
|
||||
* since: v1.14
|
||||
|
||||
### option: Locator.hover.noWaitAfter = %%-input-no-wait-after-removed-%%
|
||||
|
|
@ -1326,7 +1291,7 @@ Returns the [`element.innerHTML`](https://developer.mozilla.org/en-US/docs/Web/A
|
|||
Returns the [`element.innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText).
|
||||
|
||||
:::warning[Asserting text]
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: LocatorAssertions.toHaveText.useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
:::
|
||||
|
||||
### option: Locator.innerText.timeout = %%-input-timeout-%%
|
||||
|
|
@ -1457,7 +1422,7 @@ Boolean disabled = await page.GetByRole(AriaRole.Button).IsDisabledAsync();
|
|||
* since: v1.14
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [editable](../actionability.md#editable). If the target element is not an `<input>`, `<textarea>`, `<select>`, `[contenteditable]` and does not have a role allowing `[aria-readonly]`, this method throws an error.
|
||||
Returns whether the element is [editable](../actionability.md#editable).
|
||||
|
||||
:::warning[Asserting editable state]
|
||||
If you need to assert that an element is editable, prefer [`method: LocatorAssertions.toBeEditable`] to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
|
|
@ -1689,23 +1654,16 @@ var banana = await page.GetByRole(AriaRole.Listitem).Nth(2);
|
|||
- alias-python: or_
|
||||
- returns: <[Locator]>
|
||||
|
||||
Creates a locator matching all elements that match one or both of the two locators.
|
||||
|
||||
Note that when both locators match something, the resulting locator will have multiple matches, potentially causing a [locator strictness](../locators.md#strictness) violation.
|
||||
Creates a locator that matches either of the two locators.
|
||||
|
||||
**Usage**
|
||||
|
||||
Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly.
|
||||
|
||||
:::note
|
||||
If both "New email" button and security dialog appear on screen, the "or" locator will match both of them,
|
||||
possibly throwing the ["strict mode violation" error](../locators.md#strictness). In this case, you can use [`method: Locator.first`] to only match one of them.
|
||||
:::
|
||||
|
||||
```js
|
||||
const newEmail = page.getByRole('button', { name: 'New' });
|
||||
const dialog = page.getByText('Confirm security settings');
|
||||
await expect(newEmail.or(dialog).first()).toBeVisible();
|
||||
await expect(newEmail.or(dialog)).toBeVisible();
|
||||
if (await dialog.isVisible())
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await newEmail.click();
|
||||
|
|
@ -1714,7 +1672,7 @@ await newEmail.click();
|
|||
```java
|
||||
Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
|
||||
Locator dialog = page.getByText("Confirm security settings");
|
||||
assertThat(newEmail.or(dialog).first()).isVisible();
|
||||
assertThat(newEmail.or(dialog)).isVisible();
|
||||
if (dialog.isVisible())
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
|
||||
newEmail.click();
|
||||
|
|
@ -1723,7 +1681,7 @@ newEmail.click();
|
|||
```python async
|
||||
new_email = page.get_by_role("button", name="New")
|
||||
dialog = page.get_by_text("Confirm security settings")
|
||||
await expect(new_email.or_(dialog).first).to_be_visible()
|
||||
await expect(new_email.or_(dialog)).to_be_visible()
|
||||
if (await dialog.is_visible()):
|
||||
await page.get_by_role("button", name="Dismiss").click()
|
||||
await new_email.click()
|
||||
|
|
@ -1732,7 +1690,7 @@ await new_email.click()
|
|||
```python sync
|
||||
new_email = page.get_by_role("button", name="New")
|
||||
dialog = page.get_by_text("Confirm security settings")
|
||||
expect(new_email.or_(dialog).first).to_be_visible()
|
||||
expect(new_email.or_(dialog)).to_be_visible()
|
||||
if (dialog.is_visible()):
|
||||
page.get_by_role("button", name="Dismiss").click()
|
||||
new_email.click()
|
||||
|
|
@ -1741,7 +1699,7 @@ new_email.click()
|
|||
```csharp
|
||||
var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" });
|
||||
var dialog = page.GetByText("Confirm security settings");
|
||||
await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync();
|
||||
await Expect(newEmail.Or(dialog)).ToBeVisibleAsync();
|
||||
if (await dialog.IsVisibleAsync())
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
|
||||
await newEmail.ClickAsync();
|
||||
|
|
@ -2038,9 +1996,9 @@ Triggers a `change` and `input` event once all the provided options have been se
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
@ -2335,7 +2293,7 @@ This method expects [Locator] to point to an
|
|||
## async method: Locator.tap
|
||||
* since: v1.14
|
||||
|
||||
Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually dispatching touch events, see the [emulating legacy touch events](../touch-events.md) page.
|
||||
Perform a tap gesture on the element matching the locator.
|
||||
|
||||
**Details**
|
||||
|
||||
|
|
@ -2371,7 +2329,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Locator.tap.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.14
|
||||
|
||||
### option: Locator.tap.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Locator.tap.trial = %%-input-trial-%%
|
||||
* since: v1.14
|
||||
|
||||
## async method: Locator.textContent
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ test('status becomes submitted', async ({ page }) => {
|
|||
```
|
||||
|
||||
```java
|
||||
// ...
|
||||
...
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
|
||||
public class TestLocator {
|
||||
// ...
|
||||
...
|
||||
@Test
|
||||
void statusBecomesSubmitted() {
|
||||
// ...
|
||||
...
|
||||
page.getByRole(AriaRole.BUTTON).click();
|
||||
assertThat(page.locator(".status")).hasText("Submitted");
|
||||
}
|
||||
|
|
@ -240,24 +240,6 @@ Expected accessible description.
|
|||
### option: LocatorAssertions.NotToHaveAccessibleDescription.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.44
|
||||
|
||||
## async method: LocatorAssertions.NotToHaveAccessibleErrorMessage
|
||||
* since: v1.50
|
||||
* langs: python
|
||||
|
||||
The opposite of [`method: LocatorAssertions.toHaveAccessibleErrorMessage`].
|
||||
|
||||
### param: LocatorAssertions.NotToHaveAccessibleErrorMessage.errorMessage
|
||||
* since: v1.50
|
||||
- `errorMessage` <[string]|[RegExp]>
|
||||
|
||||
Expected accessible error message.
|
||||
|
||||
### option: LocatorAssertions.NotToHaveAccessibleErrorMessage.ignoreCase = %%-assertions-ignore-case-%%
|
||||
* since: v1.50
|
||||
|
||||
### option: LocatorAssertions.NotToHaveAccessibleErrorMessage.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.50
|
||||
|
||||
|
||||
## async method: LocatorAssertions.NotToHaveAccessibleName
|
||||
* since: v1.44
|
||||
|
|
@ -460,23 +442,6 @@ Expected options currently selected.
|
|||
### option: LocatorAssertions.NotToHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.23
|
||||
|
||||
## async method: LocatorAssertions.NotToMatchAriaSnapshot
|
||||
* since: v1.49
|
||||
* langs: python
|
||||
|
||||
The opposite of [`method: LocatorAssertions.toMatchAriaSnapshot`].
|
||||
|
||||
### param: LocatorAssertions.NotToMatchAriaSnapshot.expected
|
||||
* since: v1.49
|
||||
- `expected` <string>
|
||||
|
||||
### option: LocatorAssertions.NotToMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.49
|
||||
|
||||
### option: LocatorAssertions.NotToMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.49
|
||||
|
||||
|
||||
|
||||
## async method: LocatorAssertions.toBeAttached
|
||||
* since: v1.33
|
||||
|
|
@ -559,16 +524,6 @@ await Expect(locator).ToBeCheckedAsync();
|
|||
* since: v1.18
|
||||
- `checked` <[boolean]>
|
||||
|
||||
Provides state to assert for. Asserts for input to be checked by default.
|
||||
This option can't be used when [`option: LocatorAssertions.toBeChecked.indeterminate`] is set to true.
|
||||
|
||||
### option: LocatorAssertions.toBeChecked.indeterminate
|
||||
* since: v1.50
|
||||
- `indeterminate` <[boolean]>
|
||||
|
||||
Asserts that the element is in the indeterminate (mixed) state. Only supported for checkboxes and radio buttons.
|
||||
This option can't be true when [`option: LocatorAssertions.toBeChecked.checked`] is provided.
|
||||
|
||||
### option: LocatorAssertions.toBeChecked.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.18
|
||||
|
||||
|
|
@ -746,7 +701,7 @@ expect(locator).to_be_enabled()
|
|||
|
||||
```csharp
|
||||
var locator = Page.Locator("button.submit");
|
||||
await Expect(locator).ToBeEnabledAsync();
|
||||
await Expect(locator).toBeEnabledAsync();
|
||||
```
|
||||
|
||||
### option: LocatorAssertions.toBeEnabled.enabled
|
||||
|
|
@ -1226,7 +1181,7 @@ expect(locator).to_have_accessible_description("Save results to disk")
|
|||
|
||||
```csharp
|
||||
var locator = Page.GetByTestId("save-button");
|
||||
await Expect(locator).ToHaveAccessibleDescriptionAsync("Save results to disk");
|
||||
await Expect(locator).toHaveAccessibleDescriptionAsync("Save results to disk");
|
||||
```
|
||||
|
||||
### param: LocatorAssertions.toHaveAccessibleDescription.description
|
||||
|
|
@ -1245,56 +1200,6 @@ Expected accessible description.
|
|||
* since: v1.44
|
||||
|
||||
|
||||
## async method: LocatorAssertions.toHaveAccessibleErrorMessage
|
||||
* since: v1.50
|
||||
* langs:
|
||||
- alias-java: hasAccessibleErrorMessage
|
||||
|
||||
Ensures the [Locator] points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage).
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
const locator = page.getByTestId('username-input');
|
||||
await expect(locator).toHaveAccessibleErrorMessage('Username is required.');
|
||||
```
|
||||
|
||||
```java
|
||||
Locator locator = page.getByTestId("username-input");
|
||||
assertThat(locator).hasAccessibleErrorMessage("Username is required.");
|
||||
```
|
||||
|
||||
```python async
|
||||
locator = page.get_by_test_id("username-input")
|
||||
await expect(locator).to_have_accessible_error_message("Username is required.")
|
||||
```
|
||||
|
||||
```python sync
|
||||
locator = page.get_by_test_id("username-input")
|
||||
expect(locator).to_have_accessible_error_message("Username is required.")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var locator = Page.GetByTestId("username-input");
|
||||
await Expect(locator).ToHaveAccessibleErrorMessageAsync("Username is required.");
|
||||
```
|
||||
|
||||
### param: LocatorAssertions.toHaveAccessibleErrorMessage.errorMessage
|
||||
* since: v1.50
|
||||
- `errorMessage` <[string]|[RegExp]>
|
||||
|
||||
Expected accessible error message.
|
||||
|
||||
### option: LocatorAssertions.toHaveAccessibleErrorMessage.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.50
|
||||
|
||||
### option: LocatorAssertions.toHaveAccessibleErrorMessage.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.50
|
||||
|
||||
### option: LocatorAssertions.toHaveAccessibleErrorMessage.ignoreCase = %%-assertions-ignore-case-%%
|
||||
* since: v1.50
|
||||
|
||||
|
||||
## async method: LocatorAssertions.toHaveAccessibleName
|
||||
* since: v1.44
|
||||
* langs:
|
||||
|
|
@ -1326,7 +1231,7 @@ expect(locator).to_have_accessible_name("Save to disk")
|
|||
|
||||
```csharp
|
||||
var locator = Page.GetByTestId("save-button");
|
||||
await Expect(locator).ToHaveAccessibleNameAsync("Save to disk");
|
||||
await Expect(locator).toHaveAccessibleNameAsync("Save to disk");
|
||||
```
|
||||
|
||||
### param: LocatorAssertions.toHaveAccessibleName.name
|
||||
|
|
@ -1431,48 +1336,49 @@ Attribute name.
|
|||
* langs:
|
||||
- alias-java: hasClass
|
||||
|
||||
Ensures the [Locator] points to an element with given CSS classes. When a string is provided, it must fully match the element's `class` attribute. To match individual classes or perform partial matches, use a regular expression:
|
||||
Ensures the [Locator] points to an element with given CSS classes. This needs to be a full match
|
||||
or using a relaxed regular expression.
|
||||
|
||||
**Usage**
|
||||
|
||||
```html
|
||||
<div class='middle selected row' id='component'></div>
|
||||
<div class='selected row' id='component'></div>
|
||||
```
|
||||
|
||||
```js
|
||||
const locator = page.locator('#component');
|
||||
await expect(locator).toHaveClass('middle selected row');
|
||||
await expect(locator).toHaveClass(/(^|\s)selected(\s|$)/);
|
||||
await expect(locator).toHaveClass(/selected/);
|
||||
await expect(locator).toHaveClass('selected row');
|
||||
```
|
||||
|
||||
```java
|
||||
assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
|
||||
assertThat(page.locator("#component")).hasClass("middle selected row");
|
||||
assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
|
||||
assertThat(page.locator("#component")).hasClass("selected row");
|
||||
```
|
||||
|
||||
```python async
|
||||
from playwright.async_api import expect
|
||||
|
||||
locator = page.locator("#component")
|
||||
await expect(locator).to_have_class(re.compile(r"(^|\\s)selected(\\s|$)"))
|
||||
await expect(locator).to_have_class("middle selected row")
|
||||
await expect(locator).to_have_class(re.compile(r"selected"))
|
||||
await expect(locator).to_have_class("selected row")
|
||||
```
|
||||
|
||||
```python sync
|
||||
from playwright.sync_api import expect
|
||||
|
||||
locator = page.locator("#component")
|
||||
expect(locator).to_have_class(re.compile(r"(^|\\s)selected(\\s|$)"))
|
||||
expect(locator).to_have_class("middle selected row")
|
||||
expect(locator).to_have_class(re.compile(r"selected"))
|
||||
expect(locator).to_have_class("selected row")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var locator = Page.Locator("#component");
|
||||
await Expect(locator).ToHaveClassAsync(new Regex("(^|\\s)selected(\\s|$)"));
|
||||
await Expect(locator).ToHaveClassAsync("middle selected row");
|
||||
await Expect(locator).ToHaveClassAsync(new Regex("selected"));
|
||||
await Expect(locator).ToHaveClassAsync("selected row");
|
||||
```
|
||||
|
||||
When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected class values. Each element's class attribute is matched against the corresponding string or regular expression in the array:
|
||||
Note that if array is passed as an expected value, entire lists of elements can be asserted:
|
||||
|
||||
```js
|
||||
const locator = page.locator('list > .component');
|
||||
|
|
@ -2142,7 +2048,7 @@ await expect(locator).toHaveValues([/R/, /G/]);
|
|||
```
|
||||
|
||||
```java
|
||||
page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
|
||||
page.locator("id=favorite-colors").selectOption(["R", "G"]);
|
||||
assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
|
||||
```
|
||||
|
||||
|
|
@ -2197,91 +2103,3 @@ Expected options currently selected.
|
|||
### option: LocatorAssertions.toHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.23
|
||||
|
||||
|
||||
## async method: LocatorAssertions.toMatchAriaSnapshot
|
||||
* since: v1.49
|
||||
* langs:
|
||||
- alias-java: matchesAriaSnapshot
|
||||
|
||||
Asserts that the target element matches the given [accessibility snapshot](../aria-snapshots.md).
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
await page.goto('https://demo.playwright.dev/todomvc/');
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
`);
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.goto("https://demo.playwright.dev/todomvc/")
|
||||
await expect(page.locator('body')).to_match_aria_snapshot('''
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
''')
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.goto("https://demo.playwright.dev/todomvc/")
|
||||
expect(page.locator('body')).to_match_aria_snapshot('''
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
''')
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.GotoAsync("https://demo.playwright.dev/todomvc/");
|
||||
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"
|
||||
- heading ""todos""
|
||||
- textbox ""What needs to be done?""
|
||||
");
|
||||
```
|
||||
|
||||
```java
|
||||
page.navigate("https://demo.playwright.dev/todomvc/");
|
||||
assertThat(page.locator("body")).matchesAriaSnapshot("""
|
||||
- heading "todos"
|
||||
- textbox "What needs to be done?"
|
||||
""");
|
||||
```
|
||||
|
||||
### param: LocatorAssertions.toMatchAriaSnapshot.expected
|
||||
* since: v1.49
|
||||
- `expected` <string>
|
||||
|
||||
### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.49
|
||||
|
||||
### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.49
|
||||
|
||||
## async method: LocatorAssertions.toMatchAriaSnapshot#2
|
||||
* since: v1.50
|
||||
* langs: js
|
||||
|
||||
Asserts that the target element matches the given [accessibility snapshot](../aria-snapshots.md).
|
||||
|
||||
Snapshot is stored in a separate `.snapshot.yml` file in a location configured by `expect.toMatchAriaSnapshot.pathTemplate` and/or `snapshotPathTemplate` properties in the configuration file.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot();
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'body.snapshot.yml' });
|
||||
```
|
||||
|
||||
### option: LocatorAssertions.toMatchAriaSnapshot#2.name
|
||||
* since: v1.50
|
||||
* langs: js
|
||||
- `name` <[string]>
|
||||
|
||||
Name of the snapshot to store in the snapshot folder corresponding to this test.
|
||||
Generates sequential names if not specified.
|
||||
|
||||
### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.50
|
||||
|
||||
### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||
* since: v1.50
|
||||
|
|
|
|||
|
|
@ -812,7 +812,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Page.click.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Page.click.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Page.click.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Page.close
|
||||
|
|
@ -915,7 +915,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Page.dblclick.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Page.dblclick.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Page.dblclick.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Page.dispatchEvent
|
||||
|
|
@ -1041,9 +1041,9 @@ await page.dragAndDrop('#source', '#target', {
|
|||
```
|
||||
|
||||
```java
|
||||
page.dragAndDrop("#source", "#target");
|
||||
page.dragAndDrop("#source", '#target');
|
||||
// or specify exact positions relative to the top-left corners of the elements:
|
||||
page.dragAndDrop("#source", "#target", new Page.DragAndDropOptions()
|
||||
page.dragAndDrop("#source", '#target', new Page.DragAndDropOptions()
|
||||
.setSourcePosition(34, 7).setTargetPosition(10, 20));
|
||||
```
|
||||
|
||||
|
|
@ -1217,6 +1217,8 @@ await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches);
|
|||
// → true
|
||||
await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches);
|
||||
// → false
|
||||
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches);
|
||||
// → false
|
||||
```
|
||||
|
||||
```java
|
||||
|
|
@ -1225,6 +1227,8 @@ page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches");
|
|||
// → true
|
||||
page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches");
|
||||
// → false
|
||||
page.evaluate("() => matchMedia('(prefers-color-scheme: no-preference)').matches");
|
||||
// → false
|
||||
```
|
||||
|
||||
```python async
|
||||
|
|
@ -1233,6 +1237,8 @@ await page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches")
|
|||
# → True
|
||||
await page.evaluate("matchMedia('(prefers-color-scheme: light)').matches")
|
||||
# → False
|
||||
await page.evaluate("matchMedia('(prefers-color-scheme: no-preference)').matches")
|
||||
# → False
|
||||
```
|
||||
|
||||
```python sync
|
||||
|
|
@ -1241,6 +1247,7 @@ page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches")
|
|||
# → True
|
||||
page.evaluate("matchMedia('(prefers-color-scheme: light)').matches")
|
||||
# → False
|
||||
page.evaluate("matchMedia('(prefers-color-scheme: no-preference)').matches")
|
||||
```
|
||||
|
||||
```csharp
|
||||
|
|
@ -1249,6 +1256,8 @@ await page.EvaluateAsync("matchMedia('(prefers-color-scheme: dark)').matches");
|
|||
// → true
|
||||
await page.EvaluateAsync("matchMedia('(prefers-color-scheme: light)').matches");
|
||||
// → false
|
||||
await page.EvaluateAsync("matchMedia('(prefers-color-scheme: no-preference)').matches");
|
||||
// → false
|
||||
```
|
||||
|
||||
### option: Page.emulateMedia.media
|
||||
|
|
@ -1272,16 +1281,16 @@ Passing `'Null'` disables CSS media emulation.
|
|||
* langs: js, java
|
||||
- `colorScheme` <null|[ColorScheme]<"light"|"dark"|"no-preference">>
|
||||
|
||||
Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media feature, supported values are `'light'` and `'dark'`. Passing
|
||||
`null` disables color scheme emulation. `'no-preference'` is deprecated.
|
||||
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing
|
||||
`null` disables color scheme emulation.
|
||||
|
||||
### option: Page.emulateMedia.colorScheme
|
||||
* since: v1.9
|
||||
* langs: csharp, python
|
||||
- `colorScheme` <[ColorScheme]<"light"|"dark"|"no-preference"|"null">>
|
||||
|
||||
Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media feature, supported values are `'light'` and `'dark'`. Passing
|
||||
`'Null'` disables color scheme emulation. `'no-preference'` is deprecated.
|
||||
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing
|
||||
`'Null'` disables color scheme emulation.
|
||||
|
||||
### option: Page.emulateMedia.reducedMotion
|
||||
* since: v1.12
|
||||
|
|
@ -1309,18 +1318,6 @@ Emulates `'forced-colors'` media feature, supported values are `'active'` and `'
|
|||
* langs: csharp, python
|
||||
- `forcedColors` <[ForcedColors]<"active"|"none"|"null">>
|
||||
|
||||
### option: Page.emulateMedia.contrast
|
||||
* since: v1.51
|
||||
* langs: js, java
|
||||
- `contrast` <null|[Contrast]<"no-preference"|"more">>
|
||||
|
||||
Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. Passing `null` disables contrast emulation.
|
||||
|
||||
### option: Page.emulateMedia.contrast
|
||||
* since: v1.51
|
||||
* langs: csharp, python
|
||||
- `contrast` <[Contrast]<"no-preference"|"more"|"null">>
|
||||
|
||||
## async method: Page.evalOnSelector
|
||||
* since: v1.9
|
||||
* discouraged: This method does not wait for the element to pass actionability
|
||||
|
|
@ -1719,7 +1716,7 @@ public class Example {
|
|||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
BrowserType webkit = playwright.webkit();
|
||||
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
|
||||
Browser browser = webkit.launch({ headless: false });
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.exposeBinding("pageURL", (source, args) -> source.page().url());
|
||||
|
|
@ -1889,27 +1886,26 @@ public class Example {
|
|||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
BrowserType webkit = playwright.webkit();
|
||||
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
|
||||
Browser browser = webkit.launch({ headless: false });
|
||||
Page page = browser.newPage();
|
||||
page.exposeFunction("sha256", args -> {
|
||||
try {
|
||||
String text = (String) args[0];
|
||||
MessageDigest crypto = MessageDigest.getInstance("SHA-256");
|
||||
byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(token);
|
||||
MessageDigest crypto;
|
||||
try {
|
||||
crypto = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(token);
|
||||
});
|
||||
page.setContent(
|
||||
"<script>\n" +
|
||||
page.setContent("<script>\n" +
|
||||
" async function onClick() {\n" +
|
||||
" document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');\n" +
|
||||
" }\n" +
|
||||
"</script>\n" +
|
||||
"<button onclick=\"onClick()\">Click me</button>\n" +
|
||||
"<div></div>"
|
||||
);
|
||||
"<div></div>\n");
|
||||
page.click("button");
|
||||
}
|
||||
}
|
||||
|
|
@ -2110,7 +2106,7 @@ const frame = page.frame({ url: /.*domain.*/ });
|
|||
```
|
||||
|
||||
```java
|
||||
Frame frame = page.frameByUrl(Pattern.compile(".*domain.*"));
|
||||
Frame frame = page.frameByUrl(Pattern.compile(".*domain.*");
|
||||
```
|
||||
|
||||
```py
|
||||
|
|
@ -2337,58 +2333,6 @@ last redirect. If cannot go forward, returns `null`.
|
|||
|
||||
Navigate to the next page in history.
|
||||
|
||||
## async method: Page.requestGC
|
||||
* since: v1.48
|
||||
|
||||
Request the page to perform garbage collection. Note that there is no guarantee that all unreachable objects will be collected.
|
||||
|
||||
This is useful to help detect memory leaks. For example, if your page has a large object `'suspect'` that might be leaked, you can check that it does not leak by using a [`WeakRef`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef).
|
||||
|
||||
```js
|
||||
// 1. In your page, save a WeakRef for the "suspect".
|
||||
await page.evaluate(() => globalThis.suspectWeakRef = new WeakRef(suspect));
|
||||
// 2. Request garbage collection.
|
||||
await page.requestGC();
|
||||
// 3. Check that weak ref does not deref to the original object.
|
||||
expect(await page.evaluate(() => !globalThis.suspectWeakRef.deref())).toBe(true);
|
||||
```
|
||||
|
||||
```java
|
||||
// 1. In your page, save a WeakRef for the "suspect".
|
||||
page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)");
|
||||
// 2. Request garbage collection.
|
||||
page.requestGC();
|
||||
// 3. Check that weak ref does not deref to the original object.
|
||||
assertTrue(page.evaluate("!globalThis.suspectWeakRef.deref()"));
|
||||
```
|
||||
|
||||
```python async
|
||||
# 1. In your page, save a WeakRef for the "suspect".
|
||||
await page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)")
|
||||
# 2. Request garbage collection.
|
||||
await page.request_gc()
|
||||
# 3. Check that weak ref does not deref to the original object.
|
||||
assert await page.evaluate("!globalThis.suspectWeakRef.deref()")
|
||||
```
|
||||
|
||||
```python sync
|
||||
# 1. In your page, save a WeakRef for the "suspect".
|
||||
page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)")
|
||||
# 2. Request garbage collection.
|
||||
page.request_gc()
|
||||
# 3. Check that weak ref does not deref to the original object.
|
||||
assert page.evaluate("!globalThis.suspectWeakRef.deref()")
|
||||
```
|
||||
|
||||
```csharp
|
||||
// 1. In your page, save a WeakRef for the "suspect".
|
||||
await Page.EvaluateAsync("globalThis.suspectWeakRef = new WeakRef(suspect)");
|
||||
// 2. Request garbage collection.
|
||||
await Page.RequestGCAsync();
|
||||
// 3. Check that weak ref does not deref to the original object.
|
||||
Assert.True(await Page.EvaluateAsync("!globalThis.suspectWeakRef.deref()"));
|
||||
```
|
||||
|
||||
### option: Page.goForward.waitUntil = %%-navigation-wait-until-%%
|
||||
* since: v1.8
|
||||
|
||||
|
|
@ -2433,7 +2377,7 @@ Headless mode doesn't support navigation to a PDF document. See the
|
|||
- `url` <[string]>
|
||||
|
||||
URL to navigate page to. The url should include scheme, e.g. `https://`.
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### option: Page.goto.waitUntil = %%-navigation-wait-until-%%
|
||||
|
|
@ -2488,7 +2432,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Page.hover.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Page.hover.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Page.hover.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
### option: Page.hover.noWaitAfter = %%-input-no-wait-after-removed-%%
|
||||
|
|
@ -2640,7 +2584,7 @@ Returns whether the element is [enabled](../actionability.md#enabled).
|
|||
* discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered hidden.
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered hidden.
|
||||
|
||||
### param: Page.isHidden.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -2659,7 +2603,7 @@ Returns whether the element is hidden, the opposite of [visible](../actionabilit
|
|||
* discouraged: Use locator-based [`method: Locator.isVisible`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered not visible.
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered not visible.
|
||||
|
||||
### param: Page.isVisible.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -2765,7 +2709,8 @@ User can inspect selectors or perform manual steps while paused. Resume will con
|
|||
the place it was paused.
|
||||
|
||||
:::note
|
||||
This method requires Playwright to be started in a headed mode, with a falsy [`option: BrowserType.launch.headless`] option.
|
||||
This method requires Playwright to be started in a headed mode, with a falsy [`option: headless`] value in
|
||||
the [`method: BrowserType.launch`].
|
||||
:::
|
||||
|
||||
## async method: Page.pdf
|
||||
|
|
@ -2774,6 +2719,10 @@ This method requires Playwright to be started in a headed mode, with a falsy [`o
|
|||
|
||||
Returns the PDF buffer.
|
||||
|
||||
:::note
|
||||
Generating a pdf is currently only supported in Chromium headless.
|
||||
:::
|
||||
|
||||
`page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call
|
||||
[`method: Page.emulateMedia`] before calling `page.pdf()`:
|
||||
|
||||
|
|
@ -3138,9 +3087,11 @@ Things to keep in mind:
|
|||
|
||||
:::warning
|
||||
Running the handler will alter your page state mid-test. For example it will change the currently focused element and move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and mouse state being unchanged.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
For example, consider a test that calls [`method: Locator.focus`] followed by [`method: Keyboard.press`]. If your handler clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen on the unexpected element. Use [`method: Locator.press`] instead to avoid this problem.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
Another example is a series of mouse actions, where [`method: Mouse.move`] is followed by [`method: Mouse.down`]. Again, when the handler runs between these two actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions like [`method: Locator.click`] that do not rely on the state being unchanged by a handler.
|
||||
:::
|
||||
|
||||
|
|
@ -3161,12 +3112,12 @@ await page.getByRole('button', { name: 'Start here' }).click();
|
|||
|
||||
```java
|
||||
// Setup the handler.
|
||||
page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () -> {
|
||||
page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () => {
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click();
|
||||
});
|
||||
|
||||
// Write the test as usual.
|
||||
page.navigate("https://example.com");
|
||||
page.goto("https://example.com");
|
||||
page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
|
||||
```
|
||||
|
||||
|
|
@ -3218,12 +3169,12 @@ await page.getByRole('button', { name: 'Start here' }).click();
|
|||
|
||||
```java
|
||||
// Setup the handler.
|
||||
page.addLocatorHandler(page.getByText("Confirm your security details"), () -> {
|
||||
page.addLocatorHandler(page.getByText("Confirm your security details")), () => {
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
|
||||
});
|
||||
|
||||
// Write the test as usual.
|
||||
page.navigate("https://example.com");
|
||||
page.goto("https://example.com");
|
||||
page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
|
||||
```
|
||||
|
||||
|
|
@ -3275,12 +3226,12 @@ await page.getByRole('button', { name: 'Start here' }).click();
|
|||
|
||||
```java
|
||||
// Setup the handler.
|
||||
page.addLocatorHandler(page.locator("body"), () -> {
|
||||
page.addLocatorHandler(page.locator("body")), () => {
|
||||
page.evaluate("window.removeObstructionsForTestIfNeeded()");
|
||||
}, new Page.AddLocatorHandlerOptions().setNoWaitAfter(true));
|
||||
}, new Page.AddLocatorHandlerOptions.setNoWaitAfter(true));
|
||||
|
||||
// Write the test as usual.
|
||||
page.navigate("https://example.com");
|
||||
page.goto("https://example.com");
|
||||
page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
|
||||
```
|
||||
|
||||
|
|
@ -3326,7 +3277,7 @@ await page.addLocatorHandler(page.getByLabel('Close'), async locator => {
|
|||
```
|
||||
|
||||
```java
|
||||
page.addLocatorHandler(page.getByLabel("Close"), locator -> {
|
||||
page.addLocatorHandler(page.getByLabel("Close"), locator => {
|
||||
locator.click();
|
||||
}, new Page.AddLocatorHandlerOptions().setTimes(1));
|
||||
```
|
||||
|
|
@ -3608,7 +3559,7 @@ Enabling routing disables http cache.
|
|||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: Page.route.handler
|
||||
|
|
@ -3676,88 +3627,6 @@ When set to `minimal`, only record information necessary for routing from HAR. T
|
|||
|
||||
Optional setting to control resource content management. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file.
|
||||
|
||||
|
||||
## async method: Page.routeWebSocket
|
||||
* since: v1.48
|
||||
|
||||
This method allows to modify websocket connections that are made by the page.
|
||||
|
||||
Note that only `WebSocket`s created after this method was called will be routed. It is recommended to call this method before navigating the page.
|
||||
|
||||
**Usage**
|
||||
|
||||
Below is an example of a simple mock that responds to a single message. See [WebSocketRoute] for more details and examples.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('/ws', ws => {
|
||||
ws.onMessage(message => {
|
||||
if (message === 'request')
|
||||
ws.send('response');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("/ws", ws -> {
|
||||
ws.onMessage(frame -> {
|
||||
if ("request".equals(frame.text()))
|
||||
ws.send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
ws.on_message(lambda message: message_handler(ws, message))
|
||||
|
||||
await page.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
ws.on_message(lambda message: message_handler(ws, message))
|
||||
|
||||
page.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("/ws", ws => {
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text == "request")
|
||||
ws.Send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### param: Page.routeWebSocket.url
|
||||
* since: v1.48
|
||||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option.
|
||||
|
||||
### param: Page.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
* langs: js, python
|
||||
- `handler` <[function]\([WebSocketRoute]\): [Promise<any>|any]>
|
||||
|
||||
Handler function to route the WebSocket.
|
||||
|
||||
### param: Page.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
* langs: csharp, java
|
||||
- `handler` <[function]\([WebSocketRoute]\)>
|
||||
|
||||
Handler function to route the WebSocket.
|
||||
|
||||
|
||||
## async method: Page.screenshot
|
||||
* since: v1.8
|
||||
- returns: <[Buffer]>
|
||||
|
|
@ -3982,7 +3851,7 @@ This setting will change the default maximum time for all the methods accepting
|
|||
* since: v1.8
|
||||
- `timeout` <[float]>
|
||||
|
||||
Maximum time in milliseconds. Pass `0` to disable timeout.
|
||||
Maximum time in milliseconds
|
||||
|
||||
## async method: Page.setExtraHTTPHeaders
|
||||
* since: v1.8
|
||||
|
|
@ -4082,16 +3951,12 @@ await page.GotoAsync("https://www.microsoft.com");
|
|||
### param: Page.setViewportSize.width
|
||||
* since: v1.10
|
||||
* langs: csharp, java
|
||||
- `width` <[int]>
|
||||
|
||||
Page width in pixels.
|
||||
- `width` <[int]> page width in pixels.
|
||||
|
||||
### param: Page.setViewportSize.height
|
||||
* since: v1.10
|
||||
* langs: csharp, java
|
||||
- `height` <[int]>
|
||||
|
||||
Page height in pixels.
|
||||
- `height` <[int]> page height in pixels.
|
||||
|
||||
## async method: Page.tap
|
||||
* since: v1.8
|
||||
|
|
@ -4109,7 +3974,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
:::note
|
||||
[`method: Page.tap`] the method will throw if [`option: Browser.newContext.hasTouch`] option of the browser context is false.
|
||||
[`method: Page.tap`] the method will throw if [`option: hasTouch`] option of the browser context is false.
|
||||
:::
|
||||
|
||||
### param: Page.tap.selector = %%-input-selector-%%
|
||||
|
|
@ -4136,7 +4001,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
### option: Page.tap.timeout = %%-input-timeout-js-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Page.tap.trial = %%-input-trial-with-modifiers-%%
|
||||
### option: Page.tap.trial = %%-input-trial-%%
|
||||
* since: v1.11
|
||||
|
||||
## async method: Page.textContent
|
||||
|
|
@ -4896,7 +4761,7 @@ await page.RunAndWaitForRequestAsync(async () =>
|
|||
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]>
|
||||
|
||||
Request URL string, regex or predicate receiving [Request] object.
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: Page.waitForRequest.urlOrPredicate
|
||||
|
|
@ -5040,7 +4905,7 @@ await page.RunAndWaitForResponseAsync(async () =>
|
|||
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]>
|
||||
|
||||
Request URL string, regex or predicate receiving [Response] object.
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: Page.waitForResponse.urlOrPredicate
|
||||
|
|
@ -5049,7 +4914,7 @@ it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/We
|
|||
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]|[Promise]<[boolean]>>
|
||||
|
||||
Request URL string, regex or predicate receiving [Response] object.
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### option: Page.waitForResponse.timeout
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ test('navigates to login', async ({ page }) => {
|
|||
```
|
||||
|
||||
```java
|
||||
// ...
|
||||
...
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
|
||||
public class TestPage {
|
||||
// ...
|
||||
...
|
||||
@Test
|
||||
void navigatesToLoginPage() {
|
||||
// ...
|
||||
...
|
||||
page.getByText("Sign in").click();
|
||||
assertThat(page).hasURL(Pattern.compile(".*/login"));
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ assertThat(page).not().hasURL("error");
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Expect(Page).Not.ToHaveURLAsync("error");
|
||||
await Expect(Page).Not.ToHaveURL("error");
|
||||
```
|
||||
|
||||
## async method: PageAssertions.NotToHaveTitle
|
||||
|
|
@ -271,7 +271,7 @@ expect(page).to_have_title(re.compile(r".*checkout"))
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Expect(Page).ToHaveTitleAsync("Playwright");
|
||||
await Expect(Page).ToHaveTitle("Playwright");
|
||||
```
|
||||
|
||||
### param: PageAssertions.toHaveTitle.titleOrRegExp
|
||||
|
|
@ -296,18 +296,7 @@ Ensures the page is navigated to the given URL.
|
|||
**Usage**
|
||||
|
||||
```js
|
||||
// Check for the page URL to be 'https://playwright.dev/docs/intro' (including query string)
|
||||
await expect(page).toHaveURL('https://playwright.dev/docs/intro');
|
||||
|
||||
// Check for the page URL to contain 'doc', followed by an optional 's', followed by '/'
|
||||
await expect(page).toHaveURL(/docs?\//);
|
||||
|
||||
// Check for the predicate to be satisfied
|
||||
// For example: verify query strings
|
||||
await expect(page).toHaveURL(url => {
|
||||
const params = url.searchParams;
|
||||
return params.has('search') && params.has('options') && params.get('id') === '5';
|
||||
});
|
||||
await expect(page).toHaveURL(/.*checkout/);
|
||||
```
|
||||
|
||||
```java
|
||||
|
|
@ -331,21 +320,20 @@ expect(page).to_have_url(re.compile(".*checkout"))
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Expect(Page).ToHaveURLAsync(new Regex(".*checkout"));
|
||||
await Expect(Page).ToHaveURL(new Regex(".*checkout"));
|
||||
```
|
||||
|
||||
### param: PageAssertions.toHaveURL.url
|
||||
### param: PageAssertions.toHaveURL.urlOrRegExp
|
||||
* since: v1.18
|
||||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
- `urlOrRegExp` <[string]|[RegExp]>
|
||||
|
||||
Expected URL string, RegExp, or predicate receiving [URL] to match.
|
||||
When [`option: Browser.newContext.baseURL`] is provided via the context options and the `url` argument is a string, the two values are merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor and used for the comparison against the current browser URL.
|
||||
Expected URL string or RegExp.
|
||||
|
||||
### option: PageAssertions.toHaveURL.ignoreCase
|
||||
* since: v1.44
|
||||
- `ignoreCase` <[boolean]>
|
||||
|
||||
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression parameter if specified. A provided predicate ignores this flag.
|
||||
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified.
|
||||
|
||||
### option: PageAssertions.toHaveURL.timeout = %%-js-assertions-timeout-%%
|
||||
* since: v1.18
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ def test_status_becomes_submitted(page: Page) -> None:
|
|||
```
|
||||
|
||||
```java
|
||||
...
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
|
||||
public class TestExample {
|
||||
// ...
|
||||
...
|
||||
@Test
|
||||
void statusBecomesSubmitted() {
|
||||
// ...
|
||||
...
|
||||
page.locator("#submit-button").click();
|
||||
assertThat(page.locator(".status")).hasText("Submitted");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ await page.RouteAsync("**/*", async route =>
|
|||
|
||||
**Details**
|
||||
|
||||
The [`option: headers`] option applies to both the routed request and any redirects it initiates. However, [`option: url`], [`option: method`], and [`option: postData`] only apply to the original request and are not carried over to redirected requests.
|
||||
Note that any overrides such as [`option: url`] or [`option: headers`] only apply to the request being routed. If this request results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header through redirects, use the combination of [`method: Route.fetch`] and [`method: Route.fulfill`] instead.
|
||||
|
||||
[`method: Route.continue`] will immediately send the request to the network, other matching handlers won't be invoked. Use [`method: Route.fallback`] If you want next matching handler in the chain to be invoked.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@
|
|||
The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
|
||||
touchscreen can only be used in browser contexts that have been initialized with `hasTouch` set to true.
|
||||
|
||||
This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch events, see the [emulating legacy touch events](../touch-events.md) page.
|
||||
|
||||
## async method: Touchscreen.tap
|
||||
* since: v1.8
|
||||
|
||||
Dispatches a `touchstart` and `touchend` event with a single touch at the position ([`param: x`],[`param: y`]).
|
||||
|
||||
:::note
|
||||
[`method: Page.tap`] the method will throw if [`option: Browser.newContext.hasTouch`] option of the browser context is false.
|
||||
[`method: Page.tap`] the method will throw if [`option: hasTouch`] option of the browser context is false.
|
||||
:::
|
||||
|
||||
### param: Touchscreen.tap.x
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ await context.Tracing.StopAsync(new()
|
|||
- `name` <[string]>
|
||||
|
||||
If specified, intermediate trace files are going to be saved into the files with the
|
||||
given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory specified in [`method: BrowserType.launch`].
|
||||
given name prefix inside the [`option: tracesDir`] folder specified in [`method: BrowserType.launch`].
|
||||
To specify the final trace zip file name, you need to pass `path` option to
|
||||
[`method: Tracing.stop`] instead.
|
||||
|
||||
|
|
@ -277,84 +277,10 @@ Trace name to be shown in the Trace Viewer.
|
|||
- `name` <[string]>
|
||||
|
||||
If specified, intermediate trace files are going to be saved into the files with the
|
||||
given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory specified in [`method: BrowserType.launch`].
|
||||
given name prefix inside the [`option: tracesDir`] folder specified in [`method: BrowserType.launch`].
|
||||
To specify the final trace zip file name, you need to pass `path` option to
|
||||
[`method: Tracing.stopChunk`] instead.
|
||||
|
||||
## async method: Tracing.group
|
||||
* since: v1.49
|
||||
|
||||
:::caution
|
||||
Use `test.step` instead when available.
|
||||
:::
|
||||
|
||||
Creates a new group within the trace, assigning any subsequent API calls to this group, until [`method: Tracing.groupEnd`] is called. Groups can be nested and will be visible in the trace viewer.
|
||||
|
||||
**Usage**
|
||||
|
||||
```js
|
||||
// use test.step instead
|
||||
await test.step('Log in', async () => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
// All actions between group and groupEnd
|
||||
// will be shown in the trace viewer as a group.
|
||||
page.context().tracing().group("Open Playwright.dev > API");
|
||||
page.navigate("https://playwright.dev/");
|
||||
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
|
||||
page.context().tracing().groupEnd();
|
||||
```
|
||||
|
||||
```python sync
|
||||
# All actions between group and group_end
|
||||
# will be shown in the trace viewer as a group.
|
||||
page.context.tracing.group("Open Playwright.dev > API")
|
||||
page.goto("https://playwright.dev/")
|
||||
page.get_by_role("link", name="API").click()
|
||||
page.context.tracing.group_end()
|
||||
```
|
||||
|
||||
```python async
|
||||
# All actions between group and group_end
|
||||
# will be shown in the trace viewer as a group.
|
||||
await page.context.tracing.group("Open Playwright.dev > API")
|
||||
await page.goto("https://playwright.dev/")
|
||||
await page.get_by_role("link", name="API").click()
|
||||
await page.context.tracing.group_end()
|
||||
```
|
||||
|
||||
```csharp
|
||||
// All actions between GroupAsync and GroupEndAsync
|
||||
// will be shown in the trace viewer as a group.
|
||||
await Page.Context.Tracing.GroupAsync("Open Playwright.dev > API");
|
||||
await Page.GotoAsync("https://playwright.dev/");
|
||||
await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();
|
||||
await Page.Context.Tracing.GroupEndAsync();
|
||||
```
|
||||
|
||||
### param: Tracing.group.name
|
||||
* since: v1.49
|
||||
- `name` <[string]>
|
||||
|
||||
Group name shown in the trace viewer.
|
||||
|
||||
### option: Tracing.group.location
|
||||
* since: v1.49
|
||||
- `location` ?<[Object]>
|
||||
- `file` <[string]>
|
||||
- `line` ?<[int]>
|
||||
- `column` ?<[int]>
|
||||
|
||||
Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the [`method: Tracing.group`] call.
|
||||
|
||||
## async method: Tracing.groupEnd
|
||||
* since: v1.49
|
||||
|
||||
Closes the last group created by [`method: Tracing.group`].
|
||||
|
||||
## async method: Tracing.stop
|
||||
* since: v1.12
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
# class: WebSocket
|
||||
* since: v1.8
|
||||
|
||||
The [WebSocket] class represents WebSocket connections within a page. It provides the ability to inspect and manipulate the data being transmitted and received.
|
||||
|
||||
If you want to intercept or modify WebSocket frames, consider using [WebSocketRoute].
|
||||
The [WebSocket] class represents websocket connections in the page.
|
||||
|
||||
## event: WebSocket.close
|
||||
* since: v1.8
|
||||
|
|
|
|||
|
|
@ -1,384 +0,0 @@
|
|||
# class: WebSocketRoute
|
||||
* since: v1.48
|
||||
|
||||
Whenever a [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) route is set up with [`method: Page.routeWebSocket`] or [`method: BrowserContext.routeWebSocket`], the `WebSocketRoute` object allows to handle the WebSocket, like an actual server would do.
|
||||
|
||||
**Mocking**
|
||||
|
||||
By default, the routed WebSocket will not connect to the server. This way, you can mock entire communcation over the WebSocket. Here is an example that responds to a `"request"` with a `"response"`.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('wss://example.com/ws', ws => {
|
||||
ws.onMessage(message => {
|
||||
if (message === 'request')
|
||||
ws.send('response');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("wss://example.com/ws", ws -> {
|
||||
ws.onMessage(frame -> {
|
||||
if ("request".equals(frame.text()))
|
||||
ws.send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
await page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text == "request")
|
||||
ws.Send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Since we do not call [`method: WebSocketRoute.connectToServer`] inside the WebSocket route handler, Playwright assumes that WebSocket will be mocked, and opens the WebSocket inside the page automatically.
|
||||
|
||||
Here is another example that handles JSON messages:
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('wss://example.com/ws', ws => {
|
||||
ws.onMessage(message => {
|
||||
const json = JSON.parse(message);
|
||||
if (json.request === 'question')
|
||||
ws.send(JSON.stringify({ response: 'answer' }));
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("wss://example.com/ws", ws -> {
|
||||
ws.onMessage(frame -> {
|
||||
JsonObject json = new JsonParser().parse(frame.text()).getAsJsonObject();
|
||||
if ("question".equals(json.get("request").getAsString())) {
|
||||
Map<String, String> result = new HashMap();
|
||||
result.put("response", "answer");
|
||||
ws.send(gson.toJson(result));
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
json_message = json.loads(message)
|
||||
if json_message["request"] == "question":
|
||||
ws.send(json.dumps({ "response": "answer" }))
|
||||
|
||||
await page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
json_message = json.loads(message)
|
||||
if json_message["request"] == "question":
|
||||
ws.send(json.dumps({ "response": "answer" }))
|
||||
|
||||
page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
|
||||
ws.OnMessage(frame => {
|
||||
using var jsonDoc = JsonDocument.Parse(frame.Text);
|
||||
JsonElement root = jsonDoc.RootElement;
|
||||
if (root.TryGetProperty("request", out JsonElement requestElement) && requestElement.GetString() == "question")
|
||||
{
|
||||
var response = new Dictionary<string, string> { ["response"] = "answer" };
|
||||
string jsonResponse = JsonSerializer.Serialize(response);
|
||||
ws.Send(jsonResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
**Intercepting**
|
||||
|
||||
Alternatively, you may want to connect to the actual server, but intercept messages in-between and modify or block them. Calling [`method: WebSocketRoute.connectToServer`] returns a server-side `WebSocketRoute` instance that you can send messages to, or handle incoming messages.
|
||||
|
||||
Below is an example that modifies some messages sent by the page to the server. Messages sent from the server to the page are left intact, relying on the default forwarding.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('/ws', ws => {
|
||||
const server = ws.connectToServer();
|
||||
ws.onMessage(message => {
|
||||
if (message === 'request')
|
||||
server.send('request2');
|
||||
else
|
||||
server.send(message);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("/ws", ws -> {
|
||||
WebSocketRoute server = ws.connectToServer();
|
||||
ws.onMessage(frame -> {
|
||||
if ("request".equals(frame.text()))
|
||||
server.send("request2");
|
||||
else
|
||||
server.send(frame.text());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
server.send("request2")
|
||||
else:
|
||||
server.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
server = ws.connect_to_server()
|
||||
ws.on_message(lambda message: message_handler(server, message))
|
||||
|
||||
await page.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
server.send("request2")
|
||||
else:
|
||||
server.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
server = ws.connect_to_server()
|
||||
ws.on_message(lambda message: message_handler(server, message))
|
||||
|
||||
page.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("/ws", ws => {
|
||||
var server = ws.ConnectToServer();
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text == "request")
|
||||
server.Send("request2");
|
||||
else
|
||||
server.Send(frame.Text);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
After connecting to the server, all **messages are forwarded** between the page and the server by default.
|
||||
|
||||
However, if you call [`method: WebSocketRoute.onMessage`] on the original route, messages from the page to the server **will not be forwarded** anymore, but should instead be handled by the [`param: WebSocketRoute.onMessage.handler`].
|
||||
|
||||
Similarly, calling [`method: WebSocketRoute.onMessage`] on the server-side WebSocket will **stop forwarding messages** from the server to the page, and [`param: WebSocketRoute.onMessage.handler`] should take care of them.
|
||||
|
||||
|
||||
The following example blocks some messages in both directions. Since it calls [`method: WebSocketRoute.onMessage`] in both directions, there is no automatic forwarding at all.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('/ws', ws => {
|
||||
const server = ws.connectToServer();
|
||||
ws.onMessage(message => {
|
||||
if (message !== 'blocked-from-the-page')
|
||||
server.send(message);
|
||||
});
|
||||
server.onMessage(message => {
|
||||
if (message !== 'blocked-from-the-server')
|
||||
ws.send(message);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("/ws", ws -> {
|
||||
WebSocketRoute server = ws.connectToServer();
|
||||
ws.onMessage(frame -> {
|
||||
if (!"blocked-from-the-page".equals(frame.text()))
|
||||
server.send(frame.text());
|
||||
});
|
||||
server.onMessage(frame -> {
|
||||
if (!"blocked-from-the-server".equals(frame.text()))
|
||||
ws.send(frame.text());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def ws_message_handler(server: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message != "blocked-from-the-page":
|
||||
server.send(message)
|
||||
|
||||
def server_message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message != "blocked-from-the-server":
|
||||
ws.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
server = ws.connect_to_server()
|
||||
ws.on_message(lambda message: ws_message_handler(server, message))
|
||||
server.on_message(lambda message: server_message_handler(ws, message))
|
||||
|
||||
await page.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```python sync
|
||||
def ws_message_handler(server: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message != "blocked-from-the-page":
|
||||
server.send(message)
|
||||
|
||||
def server_message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message != "blocked-from-the-server":
|
||||
ws.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
server = ws.connect_to_server()
|
||||
ws.on_message(lambda message: ws_message_handler(server, message))
|
||||
server.on_message(lambda message: server_message_handler(ws, message))
|
||||
|
||||
page.route_web_socket("/ws", handler)
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("/ws", ws => {
|
||||
var server = ws.ConnectToServer();
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text != "blocked-from-the-page")
|
||||
server.Send(frame.Text);
|
||||
});
|
||||
server.OnMessage(frame => {
|
||||
if (frame.Text != "blocked-from-the-server")
|
||||
ws.Send(frame.Text);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
## async method: WebSocketRoute.close
|
||||
* since: v1.48
|
||||
|
||||
Closes one side of the WebSocket connection.
|
||||
|
||||
### option: WebSocketRoute.close.code
|
||||
* since: v1.48
|
||||
- `code` <[int]>
|
||||
|
||||
Optional [close code](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code).
|
||||
|
||||
### option: WebSocketRoute.close.reason
|
||||
* since: v1.48
|
||||
- `reason` <[string]>
|
||||
|
||||
Optional [close reason](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason).
|
||||
|
||||
|
||||
|
||||
## method: WebSocketRoute.connectToServer
|
||||
* since: v1.48
|
||||
- returns: <[WebSocketRoute]>
|
||||
|
||||
By default, routed WebSocket does not connect to the server, so you can mock entire WebSocket communication. This method connects to the actual WebSocket server, and returns the server-side [WebSocketRoute] instance, giving the ability to send and receive messages from the server.
|
||||
|
||||
Once connected to the server:
|
||||
* Messages received from the server will be **automatically forwarded** to the WebSocket in the page, unless [`method: WebSocketRoute.onMessage`] is called on the server-side `WebSocketRoute`.
|
||||
* Messages sent by the [`WebSocket.send()`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send) call in the page will be **automatically forwarded** to the server, unless [`method: WebSocketRoute.onMessage`] is called on the original `WebSocketRoute`.
|
||||
|
||||
See examples at the top for more details.
|
||||
|
||||
|
||||
|
||||
## method: WebSocketRoute.onClose
|
||||
* since: v1.48
|
||||
|
||||
Allows to handle [`WebSocket.close`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close).
|
||||
|
||||
By default, closing one side of the connection, either in the page or on the server, will close the other side. However, when [`method: WebSocketRoute.onClose`] handler is set up, the default forwarding of closure is disabled, and handler should take care of it.
|
||||
|
||||
### param: WebSocketRoute.onClose.handler
|
||||
* since: v1.48
|
||||
* langs: js, python
|
||||
- `handler` <[function]\([int]|[undefined], [string]|[undefined]\): [Promise<any>|any]>
|
||||
|
||||
Function that will handle WebSocket closure. Received an optional [close code](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code) and an optional [close reason](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason).
|
||||
|
||||
### param: WebSocketRoute.onClose.handler
|
||||
* since: v1.48
|
||||
* langs: java
|
||||
- `handler` <[function]\([null]|[int], [null]|[string]\)>
|
||||
|
||||
Function that will handle WebSocket closure. Received an optional [close code](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code) and an optional [close reason](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason).
|
||||
|
||||
### param: WebSocketRoute.onClose.handler
|
||||
* since: v1.48
|
||||
* langs: csharp
|
||||
- `handler` <[function]\([int?], [string]\)>
|
||||
|
||||
Function that will handle WebSocket closure. Received an optional [close code](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code) and an optional [close reason](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason).
|
||||
|
||||
## method: WebSocketRoute.onMessage
|
||||
* since: v1.48
|
||||
|
||||
This method allows to handle messages that are sent by the WebSocket, either from the page or from the server.
|
||||
|
||||
When called on the original WebSocket route, this method handles messages sent from the page. You can handle this messages by responding to them with [`method: WebSocketRoute.send`], forwarding them to the server-side connection returned by [`method: WebSocketRoute.connectToServer`] or do something else.
|
||||
|
||||
Once this method is called, messages are not automatically forwarded to the server or to the page - you should do that manually by calling [`method: WebSocketRoute.send`]. See examples at the top for more details.
|
||||
|
||||
Calling this method again will override the handler with a new one.
|
||||
|
||||
### param: WebSocketRoute.onMessage.handler
|
||||
* since: v1.48
|
||||
* langs: js, python
|
||||
- `handler` <[function]\([string]\): [Promise<any>|any]>
|
||||
|
||||
Function that will handle messages.
|
||||
|
||||
### param: WebSocketRoute.onMessage.handler
|
||||
* since: v1.48
|
||||
* langs: csharp, java
|
||||
- `handler` <[function]\([WebSocketFrame]\)>
|
||||
|
||||
Function that will handle messages.
|
||||
|
||||
|
||||
|
||||
## method: WebSocketRoute.send
|
||||
* since: v1.48
|
||||
|
||||
Sends a message to the WebSocket. When called on the original WebSocket, sends the message to the page. When called on the result of [`method: WebSocketRoute.connectToServer`], sends the message to the server. See examples at the top for more details.
|
||||
|
||||
### param: WebSocketRoute.send.message
|
||||
* since: v1.48
|
||||
- `message` <[string]|[Buffer]>
|
||||
|
||||
Message to send.
|
||||
|
||||
|
||||
|
||||
## method: WebSocketRoute.url
|
||||
* since: v1.48
|
||||
- returns: <[string]>
|
||||
|
||||
URL of the WebSocket created in the page.
|
||||
|
|
@ -136,11 +136,6 @@ defaults to 1. See [UIEvent.detail].
|
|||
|
||||
When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it.
|
||||
|
||||
## input-trial-with-modifiers
|
||||
- `trial` <[boolean]>
|
||||
|
||||
When set, this method only performs the [actionability](../actionability.md) checks and skips the action. Defaults to `false`. Useful to wait until the element is ready for the action without performing it. Note that keyboard `modifiers` will be pressed regardless of `trial` to allow testing elements which are only visible when those keys are pressed.
|
||||
|
||||
## input-source-position
|
||||
- `sourcePosition` <[Object]>
|
||||
- `x` <[float]>
|
||||
|
|
@ -259,12 +254,11 @@ Specify environment variables that will be visible to the browser. Defaults to `
|
|||
- `httpOnly` <[boolean]>
|
||||
- `secure` <[boolean]>
|
||||
- `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">> sameSite flag
|
||||
- `origins` <[Array]<[Object]>>
|
||||
- `origins` <[Array]<[Object]>> localStorage to set for context
|
||||
- `origin` <[string]>
|
||||
- `localStorage` <[Array]<[Object]>> localStorage to set for context
|
||||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `indexedDB` ?<[Array]<[unknown]>> indexedDB to set for context
|
||||
|
||||
Learn more about [storage state and auth](../auth.md).
|
||||
|
||||
|
|
@ -364,7 +358,7 @@ Target URL.
|
|||
|
||||
## js-fetch-option-params
|
||||
* langs: js
|
||||
- `params` <[Object]<[string], [string]|[float]|[boolean]>|[URLSearchParams]|[string]>
|
||||
- `params` <[Object]<[string], [string]|[number]|[boolean]>|[URLSearchParams]|[string]>
|
||||
|
||||
Query parameters to be sent with the URL.
|
||||
|
||||
|
|
@ -411,16 +405,8 @@ Request timeout in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to d
|
|||
Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
|
||||
for all status codes.
|
||||
|
||||
## js-fetch-option-form
|
||||
* langs: js
|
||||
- `form` <[Object]<[string], [string]|[float]|[boolean]>|[FormData]>
|
||||
|
||||
Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
|
||||
this request body. If this parameter is specified `content-type` header will be set to `application/x-www-form-urlencoded`
|
||||
unless explicitly provided.
|
||||
|
||||
## python-fetch-option-form
|
||||
* langs: python
|
||||
## js-python-fetch-option-form
|
||||
* langs: js, python
|
||||
- `form` <[Object]<[string], [string]|[float]|[boolean]>>
|
||||
|
||||
Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
|
||||
|
|
@ -564,6 +550,10 @@ TLS Client Authentication allows the server to request a client certificate and
|
|||
|
||||
An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, `passphrase` property should be provided if the certificate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||
|
||||
:::note
|
||||
Using Client Certificates in combination with Proxy Servers is not supported.
|
||||
:::
|
||||
|
||||
:::note
|
||||
When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`.
|
||||
:::
|
||||
|
|
@ -640,14 +630,14 @@ If no origin is specified, the username and password are sent to any servers upo
|
|||
* langs: js, java
|
||||
- `colorScheme` <null|[ColorScheme]<"light"|"dark"|"no-preference">>
|
||||
|
||||
Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media feature, supported values are `'light'` and `'dark'`. See
|
||||
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
|
||||
[`method: Page.emulateMedia`] for more details. Passing `null` resets emulation to system defaults. Defaults to `'light'`.
|
||||
|
||||
## context-option-colorscheme-csharp-python
|
||||
* langs: csharp, python
|
||||
- `colorScheme` <[ColorScheme]<"light"|"dark"|"no-preference"|"null">>
|
||||
|
||||
Emulates [prefers-colors-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) media feature, supported values are `'light'` and `'dark'`. See
|
||||
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
|
||||
[`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'light'`.
|
||||
|
||||
## context-option-reducedMotion
|
||||
|
|
@ -674,18 +664,6 @@ Emulates `'forced-colors'` media feature, supported values are `'active'`, `'non
|
|||
|
||||
Emulates `'forced-colors'` media feature, supported values are `'active'`, `'none'`. See [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'none'`.
|
||||
|
||||
## context-option-contrast
|
||||
* langs: js, java
|
||||
- `contrast` <null|[ForcedColors]<"no-preference"|"more">>
|
||||
|
||||
Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See [`method: Page.emulateMedia`] for more details. Passing `null` resets emulation to system defaults. Defaults to `'no-preference'`.
|
||||
|
||||
## context-option-contrast-csharp-python
|
||||
* langs: csharp, python
|
||||
- `contrast` <[ForcedColors]<"no-preference"|"more"|"null">>
|
||||
|
||||
Emulates `'prefers-contrast'` media feature, supported values are `'no-preference'`, `'more'`. See [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'no-preference'`.
|
||||
|
||||
## context-option-logger
|
||||
* langs: js
|
||||
- `logger` <[Logger]>
|
||||
|
|
@ -712,7 +690,7 @@ Logger sink for Playwright logging.
|
|||
- `content` ?<[HarContentPolicy]<"omit"|"embed"|"attach">> Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for all other file extensions.
|
||||
- `path` <[path]> Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by default.
|
||||
- `mode` ?<[HarMode]<"full"|"minimal">> When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
|
||||
- `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none.
|
||||
- `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none.
|
||||
|
||||
Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not
|
||||
specified, the HAR is not recorded. Make sure to await [`method: BrowserContext.close`] for the HAR to be
|
||||
|
|
@ -778,6 +756,8 @@ not recorded. Make sure to call [`method: BrowserContext.close`] for videos to b
|
|||
* langs: csharp, java, python
|
||||
- alias-python: record_video_size
|
||||
- `recordVideoSize` <[Object]>
|
||||
If `viewport` is not configured explicitly the video size defaults to 800x450. Actual picture of each page will be
|
||||
scaled down if necessary to fit the specified size.
|
||||
- `width` <[int]> Video frame width.
|
||||
- `height` <[int]> Video frame height.
|
||||
|
||||
|
|
@ -986,8 +966,6 @@ between the same pixel in compared images, between zero (strict) and one (lax),
|
|||
- %%-context-option-reducedMotion-csharp-python-%%
|
||||
- %%-context-option-forcedColors-%%
|
||||
- %%-context-option-forcedColors-csharp-python-%%
|
||||
- %%-context-option-contrast-%%
|
||||
- %%-context-option-contrast-csharp-python-%%
|
||||
- %%-context-option-logger-%%
|
||||
- %%-context-option-videospath-%%
|
||||
- %%-context-option-videosize-%%
|
||||
|
|
@ -1016,11 +994,7 @@ Additional arguments to pass to the browser instance. The list of Chromium flags
|
|||
## browser-option-channel
|
||||
- `channel` <[string]>
|
||||
|
||||
Browser distribution channel.
|
||||
|
||||
Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode).
|
||||
|
||||
Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge).
|
||||
Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary". Read more about using [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge).
|
||||
|
||||
## browser-option-chromiumsandbox
|
||||
- `chromiumSandbox` <[boolean]>
|
||||
|
|
@ -1063,7 +1037,7 @@ Close the browser process on SIGHUP. Defaults to `true`.
|
|||
Whether to run browser in headless mode. More details for
|
||||
[Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and
|
||||
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true` unless the
|
||||
[`option: BrowserType.launch.devtools`] option is `true`.
|
||||
[`option: devtools`] option is `true`.
|
||||
|
||||
## js-python-browser-option-firefoxuserprefs
|
||||
* langs: js, python
|
||||
|
|
@ -1155,11 +1129,6 @@ Note that outer and inner locators must belong to the same frame. Inner locator
|
|||
|
||||
Matches elements that do not contain specified text somewhere inside, possibly in a child or a descendant element. When passed a [string], matching is case-insensitive and searches for a substring.
|
||||
|
||||
## locator-option-visible
|
||||
- `visible` <[boolean]>
|
||||
|
||||
Only matches visible or invisible elements.
|
||||
|
||||
## locator-options-list-v1.14
|
||||
- %%-locator-option-has-text-%%
|
||||
- %%-locator-option-has-%%
|
||||
|
|
@ -1210,7 +1179,6 @@ Specify screenshot type, defaults to `png`.
|
|||
|
||||
Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with
|
||||
a pink box `#FF00FF` (customized by [`option: maskColor`]) that completely covers its bounding box.
|
||||
The mask is also applied to invisible elements, see [Matching only visible elements](../locators.md#matching-only-visible-elements) to disable that.
|
||||
|
||||
## screenshot-option-mask-color
|
||||
* since: v1.35
|
||||
|
|
@ -1513,19 +1481,19 @@ page.get_by_text(re.compile("^hello$", re.IGNORECASE))
|
|||
|
||||
```java
|
||||
// Matches <span>
|
||||
page.getByText("world");
|
||||
page.getByText("world")
|
||||
|
||||
// Matches first <div>
|
||||
page.getByText("Hello world");
|
||||
page.getByText("Hello world")
|
||||
|
||||
// Matches second <div>
|
||||
page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
|
||||
page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
|
||||
|
||||
// Matches both <div>s
|
||||
page.getByText(Pattern.compile("Hello"));
|
||||
page.getByText(Pattern.compile("Hello"))
|
||||
|
||||
// Matches second <div>
|
||||
page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
|
||||
page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
|
||||
```
|
||||
|
||||
```csharp
|
||||
|
|
@ -1779,9 +1747,7 @@ await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues");
|
|||
- `type` ?<[string]>
|
||||
* langs: js
|
||||
|
||||
This option configures a template controlling location of snapshots generated by [`method: PageAssertions.toHaveScreenshot#1`], [`method: LocatorAssertions.toMatchAriaSnapshot#2`] and [`method: SnapshotAssertions.toMatchSnapshot#1`].
|
||||
|
||||
You can configure templates for each assertion separately in [`property: TestConfig.expect`].
|
||||
This option configures a template controlling location of snapshots generated by [`method: PageAssertions.toHaveScreenshot#1`] and [`method: SnapshotAssertions.toMatchSnapshot#1`].
|
||||
|
||||
**Usage**
|
||||
|
||||
|
|
@ -1790,19 +1756,7 @@ import { defineConfig } from '@playwright/test';
|
|||
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
|
||||
// Single template for all assertions
|
||||
snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
|
||||
|
||||
// Assertion-specific templates
|
||||
expect: {
|
||||
toHaveScreenshot: {
|
||||
pathTemplate: '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
|
||||
},
|
||||
toMatchAriaSnapshot: {
|
||||
pathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -1833,22 +1787,22 @@ test.describe('suite', () => {
|
|||
|
||||
The list of supported tokens:
|
||||
|
||||
* `{arg}` - Relative snapshot path **without extension**. This comes from the arguments passed to `toHaveScreenshot()`, `toMatchAriaSnapshot()` or `toMatchSnapshot()`; if called without arguments, this will be an auto-generated snapshot name.
|
||||
* `{arg}` - Relative snapshot path **without extension**. These come from the arguments passed to the `toHaveScreenshot()` and `toMatchSnapshot()` calls; if called without arguments, this will be an auto-generated snapshot name.
|
||||
* Value: `foo/bar/baz`
|
||||
* `{ext}` - Snapshot extension (with the leading dot).
|
||||
* `{ext}` - snapshot extension (with dots)
|
||||
* Value: `.png`
|
||||
* `{platform}` - The value of `process.platform`.
|
||||
* `{projectName}` - Project's file-system-sanitized name, if any.
|
||||
* Value: `''` (empty string).
|
||||
* `{snapshotDir}` - Project's [`property: TestProject.snapshotDir`].
|
||||
* `{snapshotDir}` - Project's [`property: TestConfig.snapshotDir`].
|
||||
* Value: `/home/playwright/tests` (since `snapshotDir` is not provided in config, it defaults to `testDir`)
|
||||
* `{testDir}` - Project's [`property: TestProject.testDir`].
|
||||
* Value: `/home/playwright/tests` (absolute path since `testDir` is resolved relative to directory with config)
|
||||
* `{testDir}` - Project's [`property: TestConfig.testDir`].
|
||||
* Value: `/home/playwright/tests` (absolute path is since `testDir` is resolved relative to directory with config)
|
||||
* `{testFileDir}` - Directories in relative path from `testDir` to **test file**.
|
||||
* Value: `page`
|
||||
* `{testFileName}` - Test file name with extension.
|
||||
* Value: `page-click.spec.ts`
|
||||
* `{testFilePath}` - Relative path from `testDir` to **test file**.
|
||||
* `{testFilePath}` - Relative path from `testDir` to **test file**
|
||||
* Value: `page/page-click.spec.ts`
|
||||
* `{testName}` - File-system-sanitized test title, including parent describes but excluding file name.
|
||||
* Value: `suite-test-should-work`
|
||||
|
|
|
|||
|
|
@ -1,532 +0,0 @@
|
|||
---
|
||||
id: aria-snapshots
|
||||
title: "Snapshot testing"
|
||||
---
|
||||
import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||
|
||||
## Overview
|
||||
|
||||
With Playwright's Snapshot testing you can assert the accessibility tree of a page against a predefined snapshot template.
|
||||
|
||||
```js
|
||||
await page.goto('https://playwright.dev/');
|
||||
await expect(page.getByRole('banner')).toMatchAriaSnapshot(`
|
||||
- banner:
|
||||
- heading /Playwright enables reliable end-to-end/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- link /[\\d]+k\\+ stargazers on GitHub/
|
||||
`);
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.goto('https://playwright.dev/')
|
||||
expect(page.query_selector('banner')).to_match_aria_snapshot("""
|
||||
- banner:
|
||||
- heading /Playwright enables reliable end-to-end/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- link /[\\d]+k\\+ stargazers on GitHub/
|
||||
""")
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.goto('https://playwright.dev/')
|
||||
await expect(page.query_selector('banner')).to_match_aria_snapshot("""
|
||||
- banner:
|
||||
- heading /Playwright enables reliable end-to-end/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- link /[\\d]+k\\+ stargazers on GitHub/
|
||||
""")
|
||||
```
|
||||
|
||||
```java
|
||||
page.navigate("https://playwright.dev/");
|
||||
assertThat(page.locator("banner")).matchesAriaSnapshot("""
|
||||
- banner:
|
||||
- heading /Playwright enables reliable end-to-end/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- link /[\\d]+k\\+ stargazers on GitHub/
|
||||
""");
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.GotoAsync("https://playwright.dev/");
|
||||
await Expect(page.Locator("banner")).ToMatchAriaSnapshotAsync(@"
|
||||
- banner:
|
||||
- heading ""Playwright enables reliable end-to-end testing for modern web apps."" [level=1]
|
||||
- link ""Get started""
|
||||
- link ""Star microsoft/playwright on GitHub""
|
||||
- link /[\\d]+k\\+ stargazers on GitHub/
|
||||
");
|
||||
```
|
||||
|
||||
<LiteYouTube
|
||||
id="P4R6hnsE0UY"
|
||||
title="Getting started with ARIA Snapshots"
|
||||
/>
|
||||
|
||||
## Assertion testing vs Snapshot testing
|
||||
|
||||
Snapshot testing and assertion testing serve different purposes in test automation:
|
||||
|
||||
### Assertion testing
|
||||
Assertion testing is a targeted approach where you assert specific values or conditions about elements or components. For instance, with Playwright, [`method: LocatorAssertions.toHaveText`]
|
||||
verifies that an element contains the expected text, and [`method: LocatorAssertions.toHaveValue`]
|
||||
confirms that an input field has the expected value.
|
||||
Assertion tests are specific and generally check the current state of an element or property
|
||||
against an expected, predefined state.
|
||||
They work well for predictable, single-value checks but are limited in scope when testing the
|
||||
broader structure or variations.
|
||||
|
||||
**Advantages**
|
||||
- **Clarity**: The intent of the test is explicit and easy to understand.
|
||||
- **Specificity**: Tests focus on particular aspects of functionality, making them more robust
|
||||
against unrelated changes.
|
||||
- **Debugging**: Failures provide targeted feedback, pointing directly to the problematic aspect.
|
||||
|
||||
**Disadvantages**
|
||||
- **Verbose for complex outputs**: Writing assertions for complex data structures or large outputs
|
||||
can be cumbersome and error-prone.
|
||||
- **Maintenance overhead**: As code evolves, manually updating assertions can be time-consuming.
|
||||
|
||||
### Snapshot testing
|
||||
Snapshot testing captures a “snapshot” or representation of the entire
|
||||
state of an element, component, or data at a given moment, which is then saved for future
|
||||
comparisons. When re-running tests, the current state is compared to the snapshot, and if there
|
||||
are differences, the test fails. This approach is especially useful for complex or dynamic
|
||||
structures, where manually asserting each detail would be too time-consuming. Snapshot testing
|
||||
is broader and more holistic than assertion testing, allowing you to track more complex changes over time.
|
||||
|
||||
**Advantages**
|
||||
- **Simplifies complex outputs**: For example, testing a UI component's rendered output can be tedious with traditional assertions. Snapshots capture the entire output for easy comparison.
|
||||
- **Quick Feedback loop**: Developers can easily spot unintended changes in the output.
|
||||
- **Encourages consistency**: Helps maintain consistent output as code evolves.
|
||||
|
||||
**Disadvantages**
|
||||
- **Over-Reliance**: It can be tempting to accept changes to snapshots without fully understanding
|
||||
them, potentially hiding bugs.
|
||||
- **Granularity**: Large snapshots may be hard to interpret when differences arise, especially
|
||||
if minor changes affect large portions of the output.
|
||||
- **Suitability**: Not ideal for highly dynamic content where outputs change frequently or
|
||||
unpredictably.
|
||||
|
||||
### When to use
|
||||
|
||||
- **Snapshot testing** is ideal for:
|
||||
- UI testing of whole pages and components.
|
||||
- Broad structural checks for complex UI components.
|
||||
- Regression testing for outputs that rarely change structure.
|
||||
|
||||
- **Assertion testing** is ideal for:
|
||||
- Core logic validation.
|
||||
- Computed value testing.
|
||||
- Fine-grained tests requiring precise conditions.
|
||||
|
||||
By combining snapshot testing for broad, structural checks and assertion testing for specific functionality, you can achieve a well-rounded testing strategy.
|
||||
|
||||
## Aria snapshots
|
||||
|
||||
In Playwright, aria snapshots provide a YAML representation of the accessibility tree of a page.
|
||||
These snapshots can be stored and compared later to verify if the page structure remains consistent or meets defined
|
||||
expectations.
|
||||
|
||||
The YAML format describes the hierarchical structure of accessible elements on the page, detailing **roles**, **attributes**, **values**, and **text content**.
|
||||
The structure follows a tree-like syntax, where each node represents an accessible element, and indentation indicates
|
||||
nested elements.
|
||||
|
||||
Each accessible element in the tree is represented as a YAML node:
|
||||
|
||||
```yaml
|
||||
- role "name" [attribute=value]
|
||||
```
|
||||
|
||||
- **role**: Specifies the ARIA or HTML role of the element (e.g., `heading`, `list`, `listitem`, `button`).
|
||||
- **"name"**: Accessible name of the element. Quoted strings indicate exact values, `/patterns/` are used for regular expression.
|
||||
- **[attribute=value]**: Attributes and values, in square brackets, represent specific ARIA attributes, such
|
||||
as `checked`, `disabled`, `expanded`, `level`, `pressed`, or `selected`.
|
||||
|
||||
These values are derived from ARIA attributes or calculated based on HTML semantics. To inspect the accessibility tree
|
||||
structure of a page, use the [Chrome DevTools Accessibility Pane](https://developer.chrome.com/docs/devtools/accessibility/reference#pane).
|
||||
|
||||
|
||||
## Snapshot matching
|
||||
|
||||
The [`method: LocatorAssertions.toMatchAriaSnapshot`] assertion method in Playwright compares the accessible
|
||||
structure of the locator scope with a predefined aria snapshot template, helping validate the page's state against
|
||||
testing requirements.
|
||||
|
||||
For the following DOM:
|
||||
|
||||
```html
|
||||
<h1>title</h1>
|
||||
```
|
||||
|
||||
You can match it using the following snapshot template:
|
||||
|
||||
```js
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||
- heading "title"
|
||||
`);
|
||||
```
|
||||
|
||||
```python sync
|
||||
expect(page.locator("body")).to_match_aria_snapshot("""
|
||||
- heading "title"
|
||||
""")
|
||||
```
|
||||
|
||||
```python async
|
||||
await expect(page.locator("body")).to_match_aria_snapshot("""
|
||||
- heading "title"
|
||||
""")
|
||||
```
|
||||
|
||||
```java
|
||||
assertThat(page.locator("body")).matchesAriaSnapshot("""
|
||||
- heading "title"
|
||||
""");
|
||||
```
|
||||
|
||||
```csharp
|
||||
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"
|
||||
- heading ""title""
|
||||
");
|
||||
```
|
||||
|
||||
When matching, the snapshot template is compared to the current accessibility tree of the page:
|
||||
|
||||
* If the tree structure matches the template, the test passes; otherwise, it fails, indicating a mismatch between
|
||||
expected and actual accessibility states.
|
||||
* The comparison is case-sensitive and collapses whitespace, so indentation and line breaks are ignored.
|
||||
* The comparison is order-sensitive, meaning the order of elements in the snapshot template must match the order in the
|
||||
page's accessibility tree.
|
||||
|
||||
|
||||
### Partial matching
|
||||
|
||||
You can perform partial matches on nodes by omitting attributes or accessible names, enabling verification of specific
|
||||
parts of the accessibility tree without requiring exact matches. This flexibility is helpful for dynamic or irrelevant
|
||||
attributes.
|
||||
|
||||
```html
|
||||
<button>Submit</button>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- button
|
||||
```
|
||||
|
||||
In this example, the button role is matched, but the accessible name ("Submit") is not specified, allowing the test to
|
||||
pass regardless of the button's label.
|
||||
|
||||
<hr/>
|
||||
|
||||
For elements with ARIA attributes like `checked` or `disabled`, omitting these attributes allows partial matching,
|
||||
focusing solely on role and hierarchy.
|
||||
|
||||
```html
|
||||
<input type="checkbox" checked>
|
||||
```
|
||||
|
||||
*aria snapshot for partial match*
|
||||
|
||||
```yaml
|
||||
- checkbox
|
||||
```
|
||||
|
||||
In this partial match, the `checked` attribute is ignored, so the test will pass regardless of the checkbox state.
|
||||
|
||||
<hr/>
|
||||
|
||||
Similarly, you can partially match children in lists or groups by omitting specific list items or nested elements.
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<li>Feature A</li>
|
||||
<li>Feature B</li>
|
||||
<li>Feature C</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
*aria snapshot for partial match*
|
||||
|
||||
```yaml
|
||||
- list
|
||||
- listitem: Feature B
|
||||
```
|
||||
|
||||
Partial matches let you create flexible snapshot tests that verify essential page structure without enforcing
|
||||
specific content or attributes.
|
||||
|
||||
### Matching with regular expressions
|
||||
|
||||
Regular expressions allow flexible matching for elements with dynamic or variable text. Accessible names and text can
|
||||
support regex patterns.
|
||||
|
||||
```html
|
||||
<h1>Issues 12</h1>
|
||||
```
|
||||
|
||||
*aria snapshot with regular expression*
|
||||
|
||||
```yaml
|
||||
- heading /Issues \d+/
|
||||
```
|
||||
|
||||
## Generating snapshots
|
||||
|
||||
Creating aria snapshots in Playwright helps ensure and maintain your application's structure.
|
||||
You can generate snapshots in various ways depending on your testing setup and workflow.
|
||||
|
||||
### Generating snapshots with the Playwright code generator
|
||||
|
||||
If you're using Playwright's [Code Generator](./codegen.md), generating aria snapshots is streamlined with its
|
||||
interactive interface:
|
||||
|
||||
- **"Assert snapshot" Action**: In the code generator, you can use the "Assert snapshot" action to automatically create
|
||||
a snapshot assertion for the selected elements. This is a quick way to capture the aria snapshot as part of your
|
||||
recorded test flow.
|
||||
|
||||
- **"Aria snapshot" Tab**: The "Aria snapshot" tab within the code generator interface visually represents the
|
||||
aria snapshot for a selected locator, letting you explore, inspect, and verify element roles, attributes, and
|
||||
accessible names to aid snapshot creation and review.
|
||||
|
||||
### Updating snapshots with `@playwright/test` and the `--update-snapshots` flag
|
||||
* langs: js
|
||||
|
||||
When using the Playwright test runner (`@playwright/test`), you can automatically update snapshots with the `--update-snapshots` flag, `-u` for short.
|
||||
|
||||
Running tests with the `--update-snapshots` flag will update snapshots that did not match. Matching snapshots will not be updated.
|
||||
|
||||
```bash
|
||||
npx playwright test --update-snapshots
|
||||
```
|
||||
|
||||
Updating snapshots is useful when application structure changes require new snapshots as a baseline. Note that Playwright will wait for the maximum expect timeout specified in the test runner configuration to ensure the page is settled before taking the snapshot. It might be necessary to adjust the `--timeout` if the test hits the timeout while generating snapshots.
|
||||
|
||||
#### Empty template for snapshot generation
|
||||
|
||||
Passing an empty string as the template in an assertion generates a snapshot on-the-fly:
|
||||
|
||||
```js
|
||||
await expect(locator).toMatchAriaSnapshot('');
|
||||
```
|
||||
|
||||
Note that Playwright will wait for the maximum expect timeout specified in the test runner configuration to ensure the
|
||||
page is settled before taking the snapshot. It might be necessary to adjust the `--timeout` if the test hits the timeout
|
||||
while generating snapshots.
|
||||
|
||||
#### Snapshot patch files
|
||||
|
||||
When updating snapshots, Playwright creates patch files that capture differences. These patch files can be reviewed,
|
||||
applied, and committed to source control, allowing teams to track structural changes over time and ensure updates are
|
||||
consistent with application requirements.
|
||||
|
||||
The way source code is updated can be changed using the `--update-source-method` flag. There are several options available:
|
||||
|
||||
- **"patch"** (default): Generates a unified diff file that can be applied to the source code using `git apply`.
|
||||
- **"3way"**: Generates merge conflict markers in your source code, allowing you to choose whether to accept changes.
|
||||
- **"overwrite"**: Overwrites the source code with the new snapshot values.
|
||||
|
||||
```bash
|
||||
npx playwright test --update-snapshots --update-source-mode=3way
|
||||
```
|
||||
|
||||
#### Snapshots as separate files
|
||||
|
||||
To store your snapshots in a separate file, use the `toMatchAriaSnapshot` method with the `name` option, specifying a `.snapshot.yml` file extension.
|
||||
|
||||
```js
|
||||
await expect(page.getByRole('main')).toMatchAriaSnapshot({ name: 'main.snapshot.yml' });
|
||||
```
|
||||
|
||||
By default, snapshots from a test file `example.spec.ts` are placed in the `example.spec.ts-snapshots` directory. As snapshots should be the same across browsers, only one snapshot is saved even if testing with multiple browsers. Should you wish, you can customize the [snapshot path template](./api/class-testconfig#test-config-snapshot-path-template) using the following configuration:
|
||||
|
||||
```js
|
||||
export default defineConfig({
|
||||
expect: {
|
||||
toMatchAriaSnapshot: {
|
||||
pathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Using the `Locator.ariaSnapshot` method
|
||||
|
||||
The [`method: Locator.ariaSnapshot`] method allows you to programmatically create a YAML representation of accessible
|
||||
elements within a locator's scope, especially helpful for generating snapshots dynamically during test execution.
|
||||
|
||||
**Example**:
|
||||
|
||||
```js
|
||||
const snapshot = await page.locator('body').ariaSnapshot();
|
||||
console.log(snapshot);
|
||||
```
|
||||
|
||||
```python sync
|
||||
snapshot = page.locator("body").aria_snapshot()
|
||||
print(snapshot)
|
||||
```
|
||||
|
||||
```python async
|
||||
snapshot = await page.locator("body").aria_snapshot()
|
||||
print(snapshot)
|
||||
```
|
||||
|
||||
```java
|
||||
String snapshot = page.locator("body").ariaSnapshot();
|
||||
System.out.println(snapshot);
|
||||
```
|
||||
|
||||
```csharp
|
||||
var snapshot = await page.Locator("body").AriaSnapshotAsync();
|
||||
Console.WriteLine(snapshot);
|
||||
```
|
||||
|
||||
This command outputs the aria snapshot within the specified locator's scope in YAML format, which you can validate
|
||||
or store as needed.
|
||||
|
||||
## Accessibility tree examples
|
||||
|
||||
### Headings with level attributes
|
||||
|
||||
Headings can include a `level` attribute indicating their heading level.
|
||||
|
||||
```html
|
||||
<h1>Title</h1>
|
||||
<h2>Subtitle</h2>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- heading "Title" [level=1]
|
||||
- heading "Subtitle" [level=2]
|
||||
```
|
||||
|
||||
### Text nodes
|
||||
|
||||
Standalone or descriptive text elements appear as text nodes.
|
||||
|
||||
```html
|
||||
<div>Sample accessible name</div>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- text: Sample accessible name
|
||||
```
|
||||
|
||||
### Inline multiline text
|
||||
|
||||
Multiline text, such as paragraphs, is normalized in the aria snapshot.
|
||||
|
||||
```html
|
||||
<p>Line 1<br>Line 2</p>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- paragraph: Line 1 Line 2
|
||||
```
|
||||
|
||||
### Links
|
||||
|
||||
Links display their text or composed content from pseudo-elements.
|
||||
|
||||
```html
|
||||
<a href="#more-info">Read more about Accessibility</a>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- link "Read more about Accessibility"
|
||||
```
|
||||
|
||||
### Text boxes
|
||||
|
||||
Input elements of type `text` show their `value` attribute content.
|
||||
|
||||
```html
|
||||
<input type="text" value="Enter your name">
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- textbox: Enter your name
|
||||
```
|
||||
|
||||
### Lists with items
|
||||
|
||||
Ordered and unordered lists include their list items.
|
||||
|
||||
```html
|
||||
<ul aria-label="Main Features">
|
||||
<li>Feature 1</li>
|
||||
<li>Feature 2</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- list "Main Features":
|
||||
- listitem: Feature 1
|
||||
- listitem: Feature 2
|
||||
```
|
||||
|
||||
### Grouped elements
|
||||
|
||||
Groups capture nested elements, such as `<details>` elements with summary content.
|
||||
|
||||
```html
|
||||
<details>
|
||||
<summary>Summary</summary>
|
||||
<p>Detail content here</p>
|
||||
</details>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- group: Summary
|
||||
```
|
||||
|
||||
### Attributes and states
|
||||
|
||||
Commonly used ARIA attributes, like `checked`, `disabled`, `expanded`, `level`, `pressed`, and `selected`, represent
|
||||
control states.
|
||||
|
||||
#### Checkbox with `checked` attribute
|
||||
|
||||
```html
|
||||
<input type="checkbox" checked>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- checkbox [checked]
|
||||
```
|
||||
|
||||
#### Button with `pressed` attribute
|
||||
|
||||
```html
|
||||
<button aria-pressed="true">Toggle</button>
|
||||
```
|
||||
|
||||
*aria snapshot*
|
||||
|
||||
```yaml
|
||||
- button "Toggle" [pressed=true]
|
||||
```
|
||||
|
|
@ -232,7 +232,7 @@ await page.goto('https://github.com/login')
|
|||
# Interact with login form
|
||||
await page.get_by_label("Username or email address").fill("username")
|
||||
await page.get_by_label("Password").fill("password")
|
||||
await page.get_by_role("button", name="Sign in").click()
|
||||
await page.page.get_by_role("button", name="Sign in").click()
|
||||
# Continue with the test
|
||||
```
|
||||
|
||||
|
|
@ -266,9 +266,9 @@ existing authentication state instead.
|
|||
Playwright provides a way to reuse the signed-in state in the tests. That way you can log
|
||||
in only once and then skip the log in step for all of the tests.
|
||||
|
||||
Web apps use cookie-based or token-based authentication, where authenticated state is stored as [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) or in [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API). Playwright provides [`method: BrowserContext.storageState`] method that can be used to retrieve storage state from authenticated contexts and then create new contexts with prepopulated state.
|
||||
Web apps use cookie-based or token-based authentication, where authenticated state is stored as [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) or in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage). Playwright provides [`method: BrowserContext.storageState`] method that can be used to retrieve storage state from authenticated contexts and then create new contexts with prepopulated state.
|
||||
|
||||
Cookies, local storage and IndexedDB state can be used across different browsers. They depend on your application's authentication model which may require some combination of cookies, local storage or IndexedDB.
|
||||
Cookies and local storage state can be used across different browsers. They depend on your application's authentication model: some apps might require both cookies and local storage.
|
||||
|
||||
The following code snippet retrieves state from an authenticated context and creates a new context with that state.
|
||||
|
||||
|
|
@ -583,7 +583,7 @@ test('admin and user', async ({ adminPage, userPage }) => {
|
|||
|
||||
### Session storage
|
||||
|
||||
Reusing authenticated state covers [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) based authentication. Rarely, [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) is used for storing information associated with the signed-in state. Session storage is specific to a particular domain and is not persisted across page loads. Playwright does not provide API to persist session storage, but the following snippet can be used to save/load session storage.
|
||||
Reusing authenticated state covers [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) based authentication. Rarely, [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) is used for storing information associated with the signed-in state. Session storage is specific to a particular domain and is not persisted across page loads. Playwright does not provide API to persist session storage, but the following snippet can be used to save/load session storage.
|
||||
|
||||
```js
|
||||
// Get session storage and store as env variable
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ await page
|
|||
|
||||
#### Prefer user-facing attributes to XPath or CSS selectors
|
||||
|
||||
Your DOM can easily change so having your tests depend on your DOM structure can lead to failing tests. For example consider selecting this button by its CSS classes. Should the designer change something then the class might change, thus breaking your test.
|
||||
Your DOM can easily change so having your tests depend on your DOM structure can lead to failing tests. For example consider selecting this button by its CSS classes. Should the designer change something then the class might change breaking your test.
|
||||
|
||||
|
||||
```js
|
||||
|
|
@ -112,40 +112,10 @@ Playwright has a [test generator](./codegen.md) that can generate tests and pick
|
|||
|
||||
To pick a locator run the `codegen` command followed by the URL that you would like to pick a locator from.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright codegen playwright.dev
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright codegen playwright.dev
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright codegen playwright.dev
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
This will open a new browser window as well as the Playwright inspector. To pick a locator first click on the 'Record' button to stop the recording. By default when you run the `codegen` command it will start a new recording. Once you stop the recording the 'Pick Locator' button will be available to click.
|
||||
|
||||
You can then hover over any element on your page in the browser window and see the locator highlighted below your cursor. Clicking on an element will add the locator into the Playwright inspector. You can either copy the locator and paste into your test file or continue to explore the locator by editing it in the Playwright Inspector, for example by modifying the text, and seeing the results in the browser window.
|
||||
|
|
@ -200,40 +170,10 @@ You can live debug your test by clicking or editing the locators in your test in
|
|||
|
||||
You can also debug your tests with the Playwright inspector by running your tests with the `--debug` flag.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright test --debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright test --debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
You can then step through your test, view actionability logs and edit the locator live and see it highlighted in the browser window. This will show you which locators match, how many of them there are.
|
||||
|
||||
<img width="1350" alt="debugging with the playwright inspector" loading="lazy" src="https://user-images.githubusercontent.com/13063165/212276296-4f5b18e7-2bd7-4766-9aa5-783517bd4aa2.png" />
|
||||
|
|
@ -242,39 +182,9 @@ You can then step through your test, view actionability logs and edit the locato
|
|||
|
||||
To debug a specific test add the name of the test file and the line number of the test followed by the `--debug` flag.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright test example.spec.ts:9 --debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright test example.spec.ts:9 --debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright test example.spec.ts:9 --debug
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
#### Debugging on CI
|
||||
|
||||
For CI failures, use the Playwright [trace viewer](./trace-viewer.md) instead of videos and screenshots. The trace viewer gives you a full trace of your tests as a local Progressive Web App (PWA) that can easily be shared. With the trace viewer you can view the timeline, inspect DOM snapshots for each action using dev tools, view network requests and more.
|
||||
|
|
@ -283,75 +193,14 @@ For CI failures, use the Playwright [trace viewer](./trace-viewer.md) instead of
|
|||
|
||||
Traces are configured in the Playwright config file and are set to run on CI on the first retry of a failed test. We don't recommend setting this to `on` so that traces are run on every test as it's very performance heavy. However you can run a trace locally when developing with the `--trace` flag.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright test --trace on
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright test --trace on
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright test --trace on
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Once you run this command your traces will be recorded for each test and can be viewed directly from the HTML report.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright show-report
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright show-report
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright show-report
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
````
|
||||
|
||||
<img width="1516" alt="Playwrights HTML report" loading="lazy" src="https://user-images.githubusercontent.com/13063165/212279022-d929d4c0-2271-486a-a75f-166ac231d25f.png" />
|
||||
|
||||
|
|
@ -397,99 +246,23 @@ export default defineConfig({
|
|||
|
||||
By keeping your Playwright version up to date you will be able to test your app on the latest browser versions and catch failures before the latest browser version is released to the public.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npm install -D @playwright/test@latest
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn add --dev @playwright/test@latest
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm install --save-dev @playwright/test@latest
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Check the [release notes](./release-notes.md) to see what the latest version is and what changes have been released.
|
||||
|
||||
You can see what version of Playwright you have by running the following command.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright --version
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright --version
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright --version
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Run tests on CI
|
||||
|
||||
Setup CI/CD and run your tests frequently. The more often you run your tests the better. Ideally you should run your tests on each commit and pull request. Playwright comes with a [GitHub actions workflow](/ci-intro.md) so that tests will run on CI for you with no setup required. Playwright can also be setup on the [CI environment](/ci.md) of your choice.
|
||||
|
||||
Use Linux when running your tests on CI as it is cheaper. Developers can use whatever environment when running locally but use linux on CI. Consider setting up [Sharding](./test-sharding.md) to make CI faster.
|
||||
|
||||
|
||||
#### Optimize browser downloads on CI
|
||||
|
||||
Only install the browsers that you actually need, especially on CI. For example, if you're only testing with Chromium, install just Chromium.
|
||||
|
||||
```bash title=".github/workflows/playwright.yml"
|
||||
# Instead of installing all browsers
|
||||
npx playwright install --with-deps
|
||||
|
||||
# Install only Chromium
|
||||
npx playwright install chromium --with-deps
|
||||
```
|
||||
|
||||
This saves both download time and disk space on your CI machines.
|
||||
|
||||
### Lint your tests
|
||||
|
||||
We recommend TypeScript and linting with ESLint for your tests to catch errors early. Use [`@typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises/) [ESLint](https://eslint.org) rule to make sure there are no missing awaits before the asynchronous calls to the Playwright API. On your CI you can run `tsc --noEmit` to ensure that functions are called with the right signature.
|
||||
|
|
@ -509,40 +282,10 @@ test('runs in parallel 2', async ({ page }) => { /* ... */ });
|
|||
|
||||
Playwright can [shard](./test-parallel.md#shard-tests-between-multiple-machines) a test suite, so that it can be executed on multiple machines.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright test --shard=1/3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright test --shard=1/3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright test --shard=1/3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Productivity tips
|
||||
|
||||
### Use Soft assertions
|
||||
|
|
|
|||
|
|
@ -338,123 +338,16 @@ dotnet test --settings:webkit.runsettings
|
|||
|
||||
For Google Chrome, Microsoft Edge and other Chromium-based browsers, by default, Playwright uses open source Chromium builds. Since the Chromium project is ahead of the branded browsers, when the world is on Google Chrome N, Playwright already supports Chromium N+1 that will be released in Google Chrome and Microsoft Edge a few weeks later.
|
||||
|
||||
### Chromium: headless shell
|
||||
|
||||
Playwright ships a regular Chromium build for headed operations and a separate [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) for headless mode.
|
||||
|
||||
If you are only running tests in headless shell (i.e. the `channel` option is **not** specified), for example on CI, you can avoid downloading the full Chromium browser by passing `--only-shell` during installation.
|
||||
|
||||
```bash js
|
||||
# only running tests headlessly
|
||||
npx playwright install --with-deps --only-shell
|
||||
```
|
||||
|
||||
```bash java
|
||||
# only running tests headlessly
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps --only-shell"
|
||||
```
|
||||
|
||||
```bash python
|
||||
# only running tests headlessly
|
||||
playwright install --with-deps --only-shell
|
||||
```
|
||||
|
||||
```bash csharp
|
||||
# only running tests headlessly
|
||||
pwsh bin/Debug/netX/playwright.ps1 install --with-deps --only-shell
|
||||
```
|
||||
|
||||
### Chromium: new headless mode
|
||||
|
||||
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
|
||||
|
||||
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
|
||||
|
||||
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for details.
|
||||
|
||||
```js
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chromium' },
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setChannel("chromium"));
|
||||
Page page = browser.newPage();
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash python
|
||||
pytest test_login.py --browser-channel chromium
|
||||
```
|
||||
|
||||
```xml csharp
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<Playwright>
|
||||
<BrowserName>chromium</BrowserName>
|
||||
<LaunchOptions>
|
||||
<Channel>chromium</Channel>
|
||||
</LaunchOptions>
|
||||
</Playwright>
|
||||
</RunSettings>
|
||||
```
|
||||
|
||||
```bash csharp
|
||||
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=chromium
|
||||
```
|
||||
|
||||
With the new headless mode, you can skip downloading the headless shell during browser installation by using the `--no-shell` option:
|
||||
|
||||
```bash js
|
||||
# only running tests headlessly
|
||||
npx playwright install --with-deps --no-shell
|
||||
```
|
||||
|
||||
```bash java
|
||||
# only running tests headlessly
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps --no-shell"
|
||||
```
|
||||
|
||||
```bash python
|
||||
# only running tests headlessly
|
||||
playwright install --with-deps --no-shell
|
||||
```
|
||||
|
||||
```bash csharp
|
||||
# only running tests headlessly
|
||||
pwsh bin/Debug/netX/playwright.ps1 install --with-deps --no-shell
|
||||
```
|
||||
|
||||
### Google Chrome & Microsoft Edge
|
||||
|
||||
While Playwright can download and use the recent Chromium build, it can operate against the branded Google Chrome and Microsoft Edge browsers available on the machine (note that Playwright doesn't install them by default). In particular, the current Playwright version will support Stable and Beta channels of these browsers.
|
||||
|
||||
Available channels are `chrome`, `msedge`, `chrome-beta`, `msedge-beta`, `chrome-dev`, `msedge-dev`, `chrome-canary`, `msedge-canary`.
|
||||
Available channels are `chrome`, `msedge`, `chrome-beta`, `msedge-beta` or `msedge-dev`.
|
||||
|
||||
:::warning
|
||||
Certain Enterprise Browser Policies may impact Playwright's ability to launch and control Google Chrome and Microsoft Edge. Running in an environment with browser policies is outside of the Playwright project's scope.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Google Chrome and Microsoft Edge have switched to a [new headless mode](https://developer.chrome.com/docs/chromium/headless) implementation that is closer to a regular headed mode. This differs from [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) that is used in Playwright by default when running headless, so expect different behavior in some cases. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for details.
|
||||
:::
|
||||
|
||||
```js
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
|
|
@ -508,23 +401,6 @@ pytest test_login.py --browser-channel msedge
|
|||
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=msedge
|
||||
```
|
||||
|
||||
######
|
||||
* langs: python
|
||||
|
||||
Alternatively when using the library directly, you can specify the browser [`option: BrowserType.launch.channel`] when launching the browser:
|
||||
|
||||
```python
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
# Channel can be "chrome", "msedge", "chrome-beta", "msedge-beta" or "msedge-dev".
|
||||
browser = p.chromium.launch(channel="msedge")
|
||||
page = browser.new_page()
|
||||
page.goto("http://playwright.dev")
|
||||
print(page.title())
|
||||
browser.close()
|
||||
```
|
||||
|
||||
#### Installing Google Chrome & Microsoft Edge
|
||||
|
||||
If Google Chrome or Microsoft Edge is not available on your machine, you can install
|
||||
|
|
@ -583,13 +459,9 @@ Google Chrome and Microsoft Edge respect enterprise policies, which include limi
|
|||
|
||||
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.
|
||||
|
||||
Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems. For example, available media codecs vary substantially between Linux, macOS and Windows.
|
||||
|
||||
### WebKit
|
||||
|
||||
Playwright's WebKit is derived from the latest WebKit main branch sources, often before these updates are incorporated into Apple Safari and other WebKit-based browsers. This gives a lot of lead time to react on the potential browser update issues. Playwright doesn't work with the branded version of Safari since it relies on patches. Instead, you can test using the most recent WebKit build.
|
||||
|
||||
Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems. For example, available media codecs vary substantially between Linux, macOS and Windows. While running WebKit on Linux CI is usually the most affordable option, for the closest-to-Safari experience you should run WebKit on mac, for example if you do video playback.
|
||||
Playwright's WebKit is derived from the latest WebKit main branch sources, often before these updates are incorporated into Apple Safari and other WebKit-based browsers. This gives a lot of lead time to react on the potential browser update issues. Playwright doesn't work with the branded version of Safari since it relies on patches. Instead, you can test using the most recent WebKit build. Note that availability of certain features, which depend heavily on the underlying platform, may vary between operating systems.
|
||||
|
||||
## Install behind a firewall or a proxy
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ title: "Chrome extensions"
|
|||
Extensions only work in Chrome / Chromium launched with a persistent context. Use custom browser args at your own risk, as some of them may break Playwright functionality.
|
||||
:::
|
||||
|
||||
The snippet below retrieves the [background page](https://developer.chrome.com/extensions/background_pages) of a [Manifest v2](https://developer.chrome.com/docs/extensions/mv2/) extension whose source is located in `./my-extension`.
|
||||
|
||||
Note the use of the `chromium` channel that allows to run extensions in headless mode. Alternatively, you can launch the browser in headed mode.
|
||||
The following is code for getting a handle to the [background page](https://developer.chrome.com/extensions/background_pages) of a [Manifest v2](https://developer.chrome.com/docs/extensions/mv2/) extension whose source is located in `./my-extension`:
|
||||
|
||||
```js
|
||||
const { chromium } = require('playwright');
|
||||
|
|
@ -20,7 +18,7 @@ const { chromium } = require('playwright');
|
|||
const pathToExtension = require('path').join(__dirname, 'my-extension');
|
||||
const userDataDir = '/tmp/test-user-data-dir';
|
||||
const browserContext = await chromium.launchPersistentContext(userDataDir, {
|
||||
channel: 'chromium',
|
||||
headless: false,
|
||||
args: [
|
||||
`--disable-extensions-except=${pathToExtension}`,
|
||||
`--load-extension=${pathToExtension}`
|
||||
|
|
@ -46,7 +44,7 @@ user_data_dir = "/tmp/test-user-data-dir"
|
|||
async def run(playwright: Playwright):
|
||||
context = await playwright.chromium.launch_persistent_context(
|
||||
user_data_dir,
|
||||
channel="chromium",
|
||||
headless=False,
|
||||
args=[
|
||||
f"--disable-extensions-except={path_to_extension}",
|
||||
f"--load-extension={path_to_extension}",
|
||||
|
|
@ -80,7 +78,7 @@ user_data_dir = "/tmp/test-user-data-dir"
|
|||
def run(playwright: Playwright):
|
||||
context = playwright.chromium.launch_persistent_context(
|
||||
user_data_dir,
|
||||
channel="chromium",
|
||||
headless=False,
|
||||
args=[
|
||||
f"--disable-extensions-except={path_to_extension}",
|
||||
f"--load-extension={path_to_extension}",
|
||||
|
|
@ -103,8 +101,6 @@ with sync_playwright() as playwright:
|
|||
|
||||
To have the extension loaded when running tests you can use a test fixture to set the context. You can also dynamically retrieve the extension id and use it to load and test the popup page for example.
|
||||
|
||||
Note the use of the `chromium` channel that allows to run extensions in headless mode. Alternatively, you can launch the browser in headed mode.
|
||||
|
||||
First, add fixtures that will load the extension:
|
||||
|
||||
```js title="fixtures.ts"
|
||||
|
|
@ -118,7 +114,7 @@ export const test = base.extend<{
|
|||
context: async ({ }, use) => {
|
||||
const pathToExtension = path.join(__dirname, 'my-extension');
|
||||
const context = await chromium.launchPersistentContext('', {
|
||||
channel: 'chromium',
|
||||
headless: false,
|
||||
args: [
|
||||
`--disable-extensions-except=${pathToExtension}`,
|
||||
`--load-extension=${pathToExtension}`,
|
||||
|
|
@ -159,7 +155,7 @@ def context(playwright: Playwright) -> Generator[BrowserContext, None, None]:
|
|||
path_to_extension = Path(__file__).parent.joinpath("my-extension")
|
||||
context = playwright.chromium.launch_persistent_context(
|
||||
"",
|
||||
channel="chromium",
|
||||
headless=False,
|
||||
args=[
|
||||
f"--disable-extensions-except={path_to_extension}",
|
||||
f"--load-extension={path_to_extension}",
|
||||
|
|
@ -215,3 +211,39 @@ def test_popup_page(page: Page, extension_id: str) -> None:
|
|||
page.goto(f"chrome-extension://{extension_id}/popup.html")
|
||||
expect(page.locator("body")).to_have_text("my-extension popup")
|
||||
```
|
||||
|
||||
## Headless mode
|
||||
|
||||
:::danger
|
||||
`headless=new` mode is not officially supported by Playwright and might result in unexpected behavior.
|
||||
:::
|
||||
|
||||
By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using the following code:
|
||||
|
||||
```js title="fixtures.ts"
|
||||
// ...
|
||||
|
||||
const pathToExtension = path.join(__dirname, 'my-extension');
|
||||
const context = await chromium.launchPersistentContext('', {
|
||||
headless: false,
|
||||
args: [
|
||||
`--headless=new`,
|
||||
`--disable-extensions-except=${pathToExtension}`,
|
||||
`--load-extension=${pathToExtension}`,
|
||||
],
|
||||
});
|
||||
// ...
|
||||
```
|
||||
|
||||
```python title="conftest.py"
|
||||
path_to_extension = Path(__file__).parent.joinpath("my-extension")
|
||||
context = playwright.chromium.launch_persistent_context(
|
||||
"",
|
||||
headless=False,
|
||||
args=[
|
||||
"--headless=new",
|
||||
f"--disable-extensions-except={path_to_extension}",
|
||||
f"--load-extension={path_to_extension}",
|
||||
],
|
||||
)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Playwright tests can be run on any CI provider. This guide covers one way of run
|
|||
## Introduction
|
||||
* langs: python, java, csharp
|
||||
|
||||
Playwright tests can be run on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration.
|
||||
Playwright tests can be ran on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration.
|
||||
|
||||
#### You will learn
|
||||
* langs: python, java, csharp
|
||||
|
|
@ -322,5 +322,5 @@ This step will not work for pull requests created from a forked repository becau
|
|||
- [Learn how to perform Actions](./input.md)
|
||||
- [Learn how to write Assertions](./test-assertions.md)
|
||||
- [Learn more about the Trace Viewer](/trace-viewer.md)
|
||||
- [Learn more ways of running tests on GitHub Actions](/ci.md#github-actions)
|
||||
- [Learn more about running tests on other CI providers](/ci.md)
|
||||
- [Learn more ways of running tests on GitHub Actions](/ci.md)
|
||||
- [Learn more about running tests on other CI providers](/ci.md#github-actions) // TODO: is this link correct?
|
||||
|
|
@ -85,7 +85,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install Playwright Browsers
|
||||
|
|
@ -208,17 +208,18 @@ jobs:
|
|||
name: 'Playwright Tests'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v%%VERSION%%-noble
|
||||
options: --user 1001
|
||||
image: mcr.microsoft.com/playwright:v%%VERSION%%-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run your tests
|
||||
run: npx playwright test
|
||||
env:
|
||||
HOME: /root
|
||||
```
|
||||
|
||||
```yml python title=".github/workflows/playwright.yml"
|
||||
|
|
@ -233,8 +234,7 @@ jobs:
|
|||
name: 'Playwright Tests'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright/python:v%%VERSION%%-noble
|
||||
options: --user 1001
|
||||
image: mcr.microsoft.com/playwright/python:v%%VERSION%%-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
|
|
@ -248,6 +248,8 @@ jobs:
|
|||
pip install -e .
|
||||
- name: Run your tests
|
||||
run: pytest
|
||||
env:
|
||||
HOME: /root
|
||||
```
|
||||
|
||||
```yml java title=".github/workflows/playwright.yml"
|
||||
|
|
@ -262,8 +264,7 @@ jobs:
|
|||
name: 'Playwright Tests'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright/java:v%%VERSION%%-noble
|
||||
options: --user 1001
|
||||
image: mcr.microsoft.com/playwright/java:v%%VERSION%%-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v3
|
||||
|
|
@ -274,6 +275,8 @@ jobs:
|
|||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test
|
||||
env:
|
||||
HOME: /root
|
||||
```
|
||||
|
||||
```yml csharp title=".github/workflows/playwright.yml"
|
||||
|
|
@ -288,8 +291,7 @@ jobs:
|
|||
name: 'Playwright Tests'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright/dotnet:v%%VERSION%%-noble
|
||||
options: --user 1001
|
||||
image: mcr.microsoft.com/playwright/dotnet:v%%VERSION%%-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup dotnet
|
||||
|
|
@ -299,6 +301,8 @@ jobs:
|
|||
- run: dotnet build
|
||||
- name: Run your tests
|
||||
run: dotnet test
|
||||
env:
|
||||
HOME: /root
|
||||
```
|
||||
|
||||
#### On deployment
|
||||
|
|
@ -319,7 +323,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install Playwright
|
||||
|
|
@ -415,7 +419,7 @@ Large test suites can take very long to execute. By executing a preliminary test
|
|||
This will give you a faster feedback loop and slightly lower CI consumption while working on Pull Requests.
|
||||
To detect test files affected by your changeset, `--only-changed` analyses your suites' dependency graph. This is a heuristic and might miss tests, so it's important that you always run the full test suite after the preliminary test run.
|
||||
|
||||
```yml js title=".github/workflows/playwright.yml" {24-26}
|
||||
```yml js title=".github/workflows/playwright.yml" {20-23}
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
|
|
@ -434,7 +438,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install Playwright Browsers
|
||||
|
|
@ -454,11 +458,11 @@ jobs:
|
|||
|
||||
### Docker
|
||||
|
||||
We have a [pre-built Docker image](./docker.md) which can either be used directly or as a reference to update your existing Docker definitions.
|
||||
We have a [pre-built Docker image](./docker.md) which can either be used directly, or as a reference to update your existing Docker definitions.
|
||||
|
||||
Suggested configuration
|
||||
1. Using `--ipc=host` is also recommended when using Chromium. Without it Chromium can run out of memory
|
||||
and crash. Learn more about this option in [Docker docs](https://docs.docker.com/reference/cli/docker/container/run/#ipc).
|
||||
and crash. Learn more about this option in [Docker docs](https://docs.docker.com/engine/reference/run/#ipc-settings---ipc).
|
||||
1. Seeing other weird errors when launching Chromium? Try running your container
|
||||
with `docker run --cap-add=SYS_ADMIN` when developing locally.
|
||||
1. Using `--init` Docker flag or [dumb-init](https://github.com/Yelp/dumb-init) is recommended to avoid special
|
||||
|
|
@ -466,7 +470,7 @@ Suggested configuration
|
|||
|
||||
### Azure Pipelines
|
||||
|
||||
For Windows or macOS agents, no additional configuration is required, just install Playwright and run your tests.
|
||||
For Windows or macOS agents, no additional configuration required, just install Playwright and run your tests.
|
||||
|
||||
For Linux agents, you can use [our Docker container](./docker.md) with Azure
|
||||
Pipelines support [running containerized
|
||||
|
|
@ -766,28 +770,28 @@ Running Playwright on CircleCI is very similar to running on GitHub Actions. In
|
|||
|
||||
```yml js
|
||||
executors:
|
||||
pw-noble-development:
|
||||
pw-jammy-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v%%VERSION%%-noble
|
||||
```
|
||||
|
||||
```yml python
|
||||
executors:
|
||||
pw-noble-development:
|
||||
pw-jammy-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright/python:v%%VERSION%%-noble
|
||||
```
|
||||
|
||||
```yml java
|
||||
executors:
|
||||
pw-noble-development:
|
||||
pw-jammy-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright/java:v%%VERSION%%-noble
|
||||
```
|
||||
|
||||
```yml csharp
|
||||
executors:
|
||||
pw-noble-development:
|
||||
pw-jammy-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright/dotnet:v%%VERSION%%-noble
|
||||
```
|
||||
|
|
@ -801,10 +805,10 @@ Sharding in CircleCI is indexed with 0 which means that you will need to overrid
|
|||
|
||||
```yml
|
||||
playwright-job-name:
|
||||
executor: pw-noble-development
|
||||
executor: pw-jammy-development
|
||||
parallelism: 4
|
||||
steps:
|
||||
- run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npx playwright test --shard=${SHARD}/${CIRCLE_NODE_TOTAL}
|
||||
- run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npx playwright test -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL}
|
||||
```
|
||||
|
||||
### Jenkins
|
||||
|
|
@ -997,7 +1001,7 @@ type: docker
|
|||
|
||||
steps:
|
||||
- name: test
|
||||
image: mcr.microsoft.com/playwright:v%%VERSION%%-noble
|
||||
image: mcr.microsoft.com/playwright:v%%VERSION%%-jammy
|
||||
commands:
|
||||
- npx playwright test
|
||||
```
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@ The recommended approach is to use `setFixedTime` to set the time to a specific
|
|||
- `Event.timeStamp`
|
||||
:::
|
||||
|
||||
:::warning
|
||||
If you call `install` at any point in your test, the call _MUST_ occur before any other clock related calls (see note above for list). Calling these methods out of order will result in undefined behavior. For example, you cannot call `setInterval`, followed by `install`, then `clearInterval`, as `install` overrides the native definition of the clock functions.
|
||||
:::
|
||||
|
||||
## Test with predefined time
|
||||
|
||||
Often you only need to fake `Date.now` while keeping the timers going.
|
||||
|
|
@ -168,11 +164,11 @@ await Page.GotoAsync("http://localhost:3333");
|
|||
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
|
||||
|
||||
// Assert the page state.
|
||||
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
|
||||
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")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
|
||||
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM");
|
||||
```
|
||||
|
||||
## Test inactivity monitoring
|
||||
|
|
|
|||
|
|
@ -164,19 +164,19 @@ You can use the test generator to generate tests using emulation so as to genera
|
|||
Playwright opens a browser window with its viewport set to a specific width and height and is not responsive as tests need to be run under the same conditions. Use the `--viewport` option to generate tests with a different viewport size.
|
||||
|
||||
```bash js
|
||||
npx playwright codegen --viewport-size="800,600" playwright.dev
|
||||
npx playwright codegen --viewport-size=800,600 playwright.dev
|
||||
```
|
||||
|
||||
```bash java
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --viewport-size='800,600' playwright.dev"
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --viewport-size=800,600 playwright.dev"
|
||||
```
|
||||
|
||||
```bash python
|
||||
playwright codegen --viewport-size="800,600" playwright.dev
|
||||
playwright codegen --viewport-size=800,600 playwright.dev
|
||||
```
|
||||
|
||||
```bash csharp
|
||||
pwsh bin/Debug/netX/playwright.ps1 codegen --viewport-size="800,600" playwright.dev
|
||||
pwsh bin/Debug/netX/playwright.ps1 codegen --viewport-size=800,600 playwright.dev
|
||||
```
|
||||
######
|
||||
* langs: js
|
||||
|
|
@ -325,7 +325,7 @@ pwsh bin/Debug/netX/playwright.ps1 codegen --timezone="Europe/Rome" --geolocatio
|
|||
|
||||
### Preserve authenticated state
|
||||
|
||||
Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) data at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests.
|
||||
Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests.
|
||||
|
||||
```bash js
|
||||
npx playwright codegen github.com/microsoft/playwright --save-storage=auth.json
|
||||
|
|
@ -375,7 +375,7 @@ Make sure you only use the `auth.json` locally as it contains sensitive informat
|
|||
|
||||
#### Load authenticated state
|
||||
|
||||
Run with `--load-storage` to consume the previously loaded storage from the `auth.json`. This way, all [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) data will be restored, bringing most web apps to the authenticated state without the need to login again. This means you can continue generating tests from the logged in state.
|
||||
Run with `--load-storage` to consume the previously loaded storage from the `auth.json`. This way, all [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) will be restored, bringing most web apps to the authenticated state without the need to login again. This means you can continue generating tests from the logged in state.
|
||||
|
||||
```bash js
|
||||
npx playwright codegen --load-storage=auth.json github.com/microsoft/playwright
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ will reset pre-configured user agent and device emulation.
|
|||
Playwright runs browsers in headless mode by default. To change this behavior,
|
||||
use `headless: false` as a launch option.
|
||||
|
||||
You can also use the [`option: BrowserType.launch.slowMo`] option
|
||||
You can also use the [`option: slowMo`] option
|
||||
to slow down execution (by N milliseconds per operation) and follow along while debugging.
|
||||
|
||||
```js
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ If there is no listener for [`event: Page.dialog`], all dialogs are automaticall
|
|||
|
||||
## beforeunload dialog
|
||||
|
||||
When [`method: Page.close`] is invoked with the truthy [`option: Page.close.runBeforeUnload`] value, the page runs its unload handlers. This is the only case when [`method: Page.close`] does not wait for the page to actually close, because it might be that the page stays open in the end of the operation.
|
||||
When [`method: Page.close`] is invoked with the truthy [`option: runBeforeUnload`] value, the page runs its unload handlers. This is the only case when [`method: Page.close`] does not wait for the page to actually close, because it might be that the page stays open in the end of the operation.
|
||||
|
||||
You can register a dialog handler to handle the `beforeunload` dialog yourself:
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ title: "Docker"
|
|||
|
||||
## Introduction
|
||||
|
||||
[Dockerfile.noble] can be used to run Playwright scripts in Docker environment. This image includes the [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies). The Playwright package/dependency is not included in the image and should be installed separately.
|
||||
[Dockerfile.jammy] can be used to run Playwright scripts in Docker environment. This image includes the [Playwright browsers](./browsers.md#install-browsers) and [browser system dependencies](./browsers.md#install-system-dependencies). The Playwright package/dependency is not included in the image and should be installed separately.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -103,88 +103,6 @@ Using `--ipc=host` is recommended when using Chrome ([Docker docs](https://docs.
|
|||
|
||||
See our [Continuous Integration guides](./ci.md) for sample configs.
|
||||
|
||||
### Remote Connection
|
||||
|
||||
You can run Playwright Server in Docker while keeping your tests running on the host system or another machine. This is useful for running tests on unsupported Linux distributions or remote execution scenarios.
|
||||
|
||||
#### Running the Playwright Server
|
||||
|
||||
Start the Playwright Server in Docker:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 --rm --init -it --workdir /home/pwuser --user pwuser mcr.microsoft.com/playwright:v%%VERSION%%-noble /bin/sh -c "npx -y playwright@%%VERSION%% run-server --port 3000 --host 0.0.0.0"
|
||||
```
|
||||
|
||||
#### Connecting to the Server
|
||||
* langs: js
|
||||
|
||||
There are two ways to connect to the remote Playwright server:
|
||||
|
||||
1. Using environment variable with `@playwright/test`:
|
||||
|
||||
```bash
|
||||
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:3000/ npx playwright test
|
||||
```
|
||||
|
||||
2. Using the [`method: BrowserType.connect`] API for other applications:
|
||||
|
||||
```js
|
||||
const browser = await playwright['chromium'].connect('ws://127.0.0.1:3000/');
|
||||
```
|
||||
|
||||
#### Connecting to the Server
|
||||
* langs: python, csharp, java
|
||||
|
||||
```python sync
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.connect("ws://127.0.0.1:3000/")
|
||||
```
|
||||
|
||||
```python async
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.connect("ws://127.0.0.1:3000/")
|
||||
```
|
||||
|
||||
```csharp
|
||||
using Microsoft.Playwright;
|
||||
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.Chromium.ConnectAsync("ws://127.0.0.1:3000/");
|
||||
```
|
||||
|
||||
```java
|
||||
package org.example;
|
||||
|
||||
import com.microsoft.playwright.*;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.chromium().connect("ws://127.0.0.1:3000/");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Network Configuration
|
||||
|
||||
If you need to access local servers from within the Docker container:
|
||||
|
||||
```bash
|
||||
docker run --add-host=hostmachine:host-gateway -p 3000:3000 --rm --init -it --workdir /home/pwuser --user pwuser mcr.microsoft.com/playwright:v%%VERSION%%-noble /bin/sh -c "npx -y playwright@%%VERSION%% run-server --port 3000 --host 0.0.0.0"
|
||||
```
|
||||
|
||||
This makes `hostmachine` point to the host's localhost. Your tests should use `hostmachine` instead of `localhost` when accessing local servers.
|
||||
|
||||
:::note
|
||||
When running tests remotely, ensure the Playwright version in your tests matches the version running in the Docker container.
|
||||
:::
|
||||
|
||||
## Image tags
|
||||
|
||||
See [all available image tags].
|
||||
|
|
@ -193,6 +111,7 @@ We currently publish images with the following tags:
|
|||
- `:v%%VERSION%%` - Playwright v%%VERSION%% release docker image based on Ubuntu 24.04 LTS (Noble Numbat).
|
||||
- `: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).
|
||||
|
||||
:::note
|
||||
It is recommended to always pin your Docker image to a specific version if possible. If the Playwright version in your Docker image does not match the version in your project/tests, Playwright will be unable to locate browser executables.
|
||||
|
|
@ -203,6 +122,7 @@ It is recommended to always pin your Docker image to a specific version if possi
|
|||
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`
|
||||
|
||||
#### Alpine
|
||||
|
||||
|
|
@ -214,7 +134,7 @@ Browser builds for Firefox and WebKit are built for the [glibc](https://en.wikip
|
|||
You can use the [.NET install script](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script) in order to install different SDK versions:
|
||||
|
||||
```bash
|
||||
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir /usr/share/dotnet --channel 9.0
|
||||
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir /usr/share/dotnet --channel 6.0
|
||||
```
|
||||
|
||||
## Build your own image
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ title: "Downloads"
|
|||
|
||||
For every attachment downloaded by the page, [`event: Page.download`] event is emitted. All these attachments are downloaded into a temporary folder. You can obtain the download url, file name and payload stream using the [Download] object from the event.
|
||||
|
||||
You can specify where to persist downloaded files using the [`option: BrowserType.launch.downloadsPath`] option in [`method: BrowserType.launch`].
|
||||
You can specify where to persist downloaded files using the [`option: downloadsPath`] option in [`method: BrowserType.launch`].
|
||||
|
||||
:::note
|
||||
Downloaded files are deleted when the browser context that produced them is closed.
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ page.setViewportSize(1600, 1200);
|
|||
// Emulate high-DPI
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setViewportSize(2560, 1440)
|
||||
.setDeviceScaleFactor(2));
|
||||
.setDeviceScaleFactor(2);
|
||||
```
|
||||
|
||||
```python async
|
||||
|
|
@ -378,7 +378,7 @@ const context = await browser.newContext({
|
|||
|
||||
```java
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setPermissions(Arrays.asList("notifications")));
|
||||
.setPermissions(Arrays.asList("notifications"));
|
||||
```
|
||||
|
||||
```python async
|
||||
|
|
@ -558,7 +558,7 @@ await context.SetGeolocationAsync(new Geolocation() { Longitude = 48.858455, Lat
|
|||
**Note** you can only change geolocation for all pages in the context.
|
||||
## Color Scheme and Media
|
||||
|
||||
Emulate the users `"colorScheme"`. Supported values are 'light' and 'dark'. You can also emulate the media type with [`method: Page.emulateMedia`].
|
||||
Emulate the users `"colorScheme"`. Supported values are 'light', 'dark', 'no-preference'. You can also emulate the media type with [`method: Page.emulateMedia`].
|
||||
|
||||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Get started by installing Playwright and generating a test to see it in action.
|
|||
|
||||
## Installation
|
||||
|
||||
Playwright has a VS Code extension which is available when testing with Node.js. Install [it from the VS Code marketplace](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) or from the extensions tab in VS Code.
|
||||
Install the [VS Code extension from the marketplace](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) or from the extensions tab in VS Code.
|
||||
|
||||

|
||||
|
||||
|
|
@ -202,7 +202,7 @@ To run the **setup** test only once, deselect it from the projects section in th
|
|||
|
||||
## Global Setup
|
||||
|
||||
**Global setup** runs when you execute your first test. It runs only once and is useful for setting up a database or starting a server. You can manually run **global setup** by clicking the `Run global setup` option from the **Setup** section in the Playwright sidebar. **Global teardown** does not run by default; you need to manually initiate it by clicking the `Run global teardown` option.
|
||||
**Global setup** tests are run when you execute your first test. This runs only once and is useful for setting up a database or starting a server. You can manually run a **global setup** test by clicking the `Run global setup` option from the **Setup** section in the Playwright sidebar. You can also run **global teardown** tests by clicking the `Run global teardown` option.
|
||||
|
||||
Global setup will re-run when you debug tests as this ensures an isolated environment and dedicated setup for the test.
|
||||
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ await page.getByText('Item').click({ button: 'right' });
|
|||
// Shift + click
|
||||
await page.getByText('Item').click({ modifiers: ['Shift'] });
|
||||
|
||||
// Ctrl + click on Windows and Linux
|
||||
// Ctrl + click or Windows and Linux
|
||||
// Meta + click on macOS
|
||||
await page.getByText('Item').click({ modifiers: ['ControlOrMeta'] });
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ page.getByText("Item").click(new Locator.ClickOptions().setButton(MouseButton.RI
|
|||
// Shift + click
|
||||
page.getByText("Item").click(new Locator.ClickOptions().setModifiers(Arrays.asList(KeyboardModifier.SHIFT)));
|
||||
|
||||
// Ctrl + click on Windows and Linux
|
||||
// Ctrl + click or Windows and Linux
|
||||
// Meta + click on macOS
|
||||
page.getByText("Item").click(new Locator.ClickOptions().setModifiers(Arrays.asList(KeyboardModifier.CONTROL_OR_META)));
|
||||
|
||||
|
|
@ -265,7 +265,7 @@ await page.get_by_text("Item").click(button="right")
|
|||
# Shift + click
|
||||
await page.get_by_text("Item").click(modifiers=["Shift"])
|
||||
|
||||
# Ctrl + click on Windows and Linux
|
||||
# Ctrl + click or Windows and Linux
|
||||
# Meta + click on macOS
|
||||
await page.get_by_text("Item").click(modifiers=["ControlOrMeta"])
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ await page.GetByText("Item").ClickAsync(new() { Button = MouseButton.Right });
|
|||
// Shift + click
|
||||
await page.GetByText("Item").ClickAsync(new() { Modifiers = new[] { KeyboardModifier.Shift } });
|
||||
|
||||
// Ctrl + click on Windows and Linux
|
||||
// Ctrl + click or Windows and Linux
|
||||
// Meta + click on macOS
|
||||
await page.GetByText("Item").ClickAsync(new() { Modifiers = new[] { KeyboardModifier.ControlOrMeta } });
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ 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 MSTest, NUnit, or xUnit [base classes](./test-runners.md) 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:
|
||||
|
||||
|
|
@ -17,7 +17,6 @@ You can choose to use MSTest, NUnit, or xUnit [base classes](./test-runners.md)
|
|||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'xUnit', value: 'xunit'},
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
|
@ -35,14 +34,6 @@ dotnet new mstest -n PlaywrightTests
|
|||
cd PlaywrightTests
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="xunit">
|
||||
|
||||
```bash
|
||||
dotnet new xunit -n PlaywrightTests
|
||||
cd PlaywrightTests
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
|
@ -54,7 +45,6 @@ cd PlaywrightTests
|
|||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'xUnit', value: 'xunit'},
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
|
@ -70,13 +60,6 @@ dotnet add package Microsoft.Playwright.NUnit
|
|||
dotnet add package Microsoft.Playwright.MSTest
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="xunit">
|
||||
|
||||
```bash
|
||||
dotnet add package Microsoft.Playwright.Xunit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
|
@ -104,7 +87,6 @@ Edit the `UnitTest1.cs` file with the code below to create an example end-to-end
|
|||
values={[
|
||||
{label: 'MSTest', value: 'mstest'},
|
||||
{label: 'NUnit', value: 'nunit'},
|
||||
{label: 'xUnit', value: 'xunit'},
|
||||
]
|
||||
}>
|
||||
<TabItem value="nunit">
|
||||
|
|
@ -182,41 +164,6 @@ public class ExampleTest : PageTest
|
|||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="xunit">
|
||||
|
||||
```csharp title="UnitTest1.cs"
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Playwright;
|
||||
using Microsoft.Playwright.Xunit;
|
||||
|
||||
namespace PlaywrightTests;
|
||||
|
||||
public class UnitTest1: PageTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task HasTitle()
|
||||
{
|
||||
await Page.GotoAsync("https://playwright.dev");
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStartedLink()
|
||||
{
|
||||
await Page.GotoAsync("https://playwright.dev");
|
||||
|
||||
// Click the get started link.
|
||||
await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync();
|
||||
|
||||
// Expects page to have a heading with the name of Installation.
|
||||
await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Running the Example Tests
|
||||
|
|
@ -233,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 13 Ventura, or later.
|
||||
- Debian 12, Ubuntu 22.04, Ubuntu 24.04, on x86-64 and 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
|
||||
|
||||
|
|
@ -243,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 MSTest, NUnit, and xUnit base classes](./test-runners.md)
|
||||
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public class App {
|
|||
}
|
||||
```
|
||||
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, [`option: BrowserType.launch.headless`] option to `false`. You can also use [`option: BrowserType.launch.slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, pass the `setHeadless(false)` flag while launching the browser. You can also use [`option: slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
|
||||
```java
|
||||
playwright.firefox().launch(new BrowserType.LaunchOptions().setHeadless(false).setSlowMo(50));
|
||||
|
|
@ -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 13 Ventura, or later.
|
||||
- Debian 12, Ubuntu 22.04, Ubuntu 24.04, on x86-64 and 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
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ The `tests` folder contains a basic example test to help you get started with te
|
|||
|
||||
## Running the Example Test
|
||||
|
||||
By default tests will be run on all 3 browsers, Chromium, Firefox and WebKit using 3 workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
|
||||
By default tests will be run on all 3 browsers, chromium, firefox and webkit using 3 workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
|
|
@ -286,10 +286,10 @@ pnpm exec playwright --version
|
|||
|
||||
## System requirements
|
||||
|
||||
- Latest version of Node.js 18, 20 or 22.
|
||||
- Node.js 18+
|
||||
- Windows 10+, Windows Server 2016+ or Windows Subsystem for Linux (WSL).
|
||||
- macOS 13 Ventura, or later.
|
||||
- Debian 12, Ubuntu 22.04, Ubuntu 24.04, on x86-64 and 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 13 Ventura, or later.
|
||||
- Debian 12, Ubuntu 22.04, Ubuntu 24.04, on x86-64 and 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 MSTest, NUnit, and xUnit [base classes](https://playwright.dev/dotnet/docs/test-runners) 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 [MSTest, NUnit, or xUnit base classes](./test-runners.md) 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
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ Now run it.
|
|||
dotnet run
|
||||
```
|
||||
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, set [`option: BrowserType.launch.headless`] option to `false`. You can also use [`option: BrowserType.launch.slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, pass the `Headless = false` flag while launching the browser. You can also use [`option: slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
|
||||
```csharp
|
||||
await using var browser = await playwright.Firefox.LaunchAsync(new()
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ The key differences to note are as follows:
|
|||
| `import` from | `playwright` | `@playwright/test` |
|
||||
| Initialization | Explicitly need to: <ol><li>Pick a browser to use, e.g. `chromium`</li><li>Launch browser with [`method: BrowserType.launch`]</li><li>Create a context with [`method: Browser.newContext`], <em>and</em> pass any context options explicitly, e.g. `devices['iPhone 11']`</li><li>Create a page with [`method: BrowserContext.newPage`]</li></ol> | An isolated `page` and `context` are provided to each test out-of the box, along with other [built-in fixtures](./test-fixtures.md#built-in-fixtures). No explicit creation. If referenced by the test in its arguments, the Test Runner will create them for the test. (i.e. lazy-initialization) |
|
||||
| Assertions | No built-in Web-First Assertions | [Web-First assertions](./test-assertions.md) like: <ul><li>[`method: PageAssertions.toHaveTitle`]</li><li>[`method: PageAssertions.toHaveScreenshot#1`]</li></ul> which auto-wait and retry for the condition to be met.|
|
||||
| Timeouts | Defaults to 30s for most operations. | Most operations don't time out, but every test has a timeout that makes it fail (30s by default). |
|
||||
| Cleanup | Explicitly need to: <ol><li>Close context with [`method: BrowserContext.close`]</li><li>Close browser with [`method: Browser.close`]</li></ol> | No explicit close of [built-in fixtures](./test-fixtures.md#built-in-fixtures); the Test Runner will take care of it.
|
||||
| Running | When using the Library, you run the code as a node script, possibly with some compilation first. | When using the Test Runner, you use the `npx playwright test` command. Along with your [config](./test-configuration.md), the Test Runner handles any compilation and choosing what to run and how to run it. |
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ with sync_playwright() as p:
|
|||
browser.close()
|
||||
```
|
||||
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, set [`option: BrowserType.launch.headless`] option to `False`. You can also use [`option: BrowserType.launch.slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, pass the `headless=False` flag while launching the browser. You can also use [`option: slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
|
||||
```py
|
||||
firefox.launch(headless=False, slow_mo=50)
|
||||
|
|
|
|||
|
|
@ -62,11 +62,11 @@ expect(page.get_by_text("Welcome, John!")).to_be_visible()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.GetByLabel("User Name").FillAsync("John");
|
||||
await page.GetByLabel("User Name").FillAsync("John");
|
||||
|
||||
await Page.GetByLabel("Password").FillAsync("secret-password");
|
||||
await page.GetByLabel("Password").FillAsync("secret-password");
|
||||
|
||||
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
|
||||
|
||||
await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync();
|
||||
```
|
||||
|
|
@ -101,7 +101,7 @@ page.get_by_role("button", name="Sign in").click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
|
||||
```
|
||||
|
||||
:::note
|
||||
|
|
@ -122,7 +122,7 @@ await locator.click();
|
|||
|
||||
```java
|
||||
Locator locator = page.getByRole(AriaRole.BUTTON,
|
||||
new Page.GetByRoleOptions().setName("Sign in"));
|
||||
new Page.GetByRoleOptions().setName("Sign in"))
|
||||
|
||||
locator.hover();
|
||||
locator.click();
|
||||
|
|
@ -143,7 +143,7 @@ locator.click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
var locator = Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
|
||||
var locator = page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
|
||||
|
||||
await locator.HoverAsync();
|
||||
await locator.ClickAsync();
|
||||
|
|
@ -180,7 +180,7 @@ locator.click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
var locator = Page
|
||||
var locator = page
|
||||
.FrameLocator("#my-frame")
|
||||
.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
|
||||
|
||||
|
|
@ -249,11 +249,11 @@ await Expect(Page
|
|||
.GetByRole(AriaRole.Heading, new() { Name = "Sign up" }))
|
||||
.ToBeVisibleAsync();
|
||||
|
||||
await Page
|
||||
await page
|
||||
.GetByRole(AriaRole.Checkbox, new() { Name = "Subscribe" })
|
||||
.CheckAsync();
|
||||
|
||||
await Page
|
||||
await page
|
||||
.GetByRole(AriaRole.Button, new() {
|
||||
NameRegex = new Regex("submit", RegexOptions.IgnoreCase)
|
||||
})
|
||||
|
|
@ -298,7 +298,7 @@ page.get_by_label("Password").fill("secret")
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.GetByLabel("Password").FillAsync("secret");
|
||||
await page.GetByLabel("Password").FillAsync("secret");
|
||||
```
|
||||
|
||||
:::note[When to use label locators]
|
||||
|
|
@ -335,7 +335,7 @@ page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page
|
||||
await page
|
||||
.GetByPlaceholder("name@example.com")
|
||||
.FillAsync("playwright@microsoft.com");
|
||||
```
|
||||
|
|
@ -468,7 +468,7 @@ page.get_by_alt_text("playwright logo").click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.GetByAltText("playwright logo").ClickAsync();
|
||||
await page.GetByAltText("playwright logo").ClickAsync();
|
||||
```
|
||||
|
||||
:::note[When to use alt locators]
|
||||
|
|
@ -540,7 +540,7 @@ page.get_by_test_id("directions").click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.GetByTestId("directions").ClickAsync();
|
||||
await page.GetByTestId("directions").ClickAsync();
|
||||
```
|
||||
|
||||
:::note[When to use testid locators]
|
||||
|
|
@ -604,7 +604,7 @@ page.get_by_test_id("directions").click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.GetByTestId("directions").ClickAsync();
|
||||
await page.GetByTestId("directions").ClickAsync();
|
||||
```
|
||||
|
||||
### Locate by CSS or XPath
|
||||
|
|
@ -644,11 +644,11 @@ page.locator("//button").click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.Locator("css=button").ClickAsync();
|
||||
await Page.Locator("xpath=//button").ClickAsync();
|
||||
await page.Locator("css=button").ClickAsync();
|
||||
await page.Locator("xpath=//button").ClickAsync();
|
||||
|
||||
await Page.Locator("button").ClickAsync();
|
||||
await Page.Locator("//button").ClickAsync();
|
||||
await page.Locator("button").ClickAsync();
|
||||
await page.Locator("//button").ClickAsync();
|
||||
```
|
||||
|
||||
XPath and CSS selectors can be tied to the DOM structure or implementation. These selectors can break when the DOM structure changes. Long CSS or XPath chains below are an example of a **bad practice** that leads to unstable tests:
|
||||
|
|
@ -688,9 +688,9 @@ page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
|
|||
```
|
||||
|
||||
```csharp
|
||||
await Page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
|
||||
await page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
|
||||
|
||||
await Page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
|
||||
await page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
|
||||
```
|
||||
|
||||
:::note[When to use this]
|
||||
|
|
@ -946,7 +946,7 @@ page.getByRole(AriaRole.LISTITEM)
|
|||
.setName("Product 2"))))
|
||||
.getByRole(AriaRole.BUTTON,
|
||||
new Page.GetByRoleOptions().setName("Add to cart"))
|
||||
.click();
|
||||
.click()
|
||||
```
|
||||
|
||||
```python async
|
||||
|
|
@ -987,7 +987,7 @@ assertThat(page
|
|||
.getByRole(AriaRole.LISTITEM)
|
||||
.filter(new Locator.FilterOptions()
|
||||
.setHas(page.GetByRole(AriaRole.HEADING,
|
||||
new Page.GetByRoleOptions().setName("Product 2")))))
|
||||
new Page.GetByRoleOptions().setName("Product 2"))))
|
||||
.hasCount(1);
|
||||
```
|
||||
|
||||
|
|
@ -1033,7 +1033,7 @@ assertThat(page
|
|||
.filter(new Locator.FilterOptions()
|
||||
.setHas(page.GetByRole(AriaRole.LIST)
|
||||
.GetByRole(AriaRole.HEADING,
|
||||
new Page.GetByRoleOptions().setName("Product 2")))))
|
||||
new Page.GetByRoleOptions().setName("Product 2"))))
|
||||
.hasCount(1);
|
||||
```
|
||||
|
||||
|
|
@ -1079,7 +1079,7 @@ await expect(page
|
|||
```java
|
||||
assertThat(page
|
||||
.getByRole(AriaRole.LISTITEM)
|
||||
.filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2"))))
|
||||
.filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2")))
|
||||
.hasCount(1);
|
||||
```
|
||||
|
||||
|
|
@ -1218,7 +1218,7 @@ var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
|
|||
|
||||
### Matching one of the two alternative locators
|
||||
|
||||
If you'd like to target one of the two or more elements, and you don't know which one it will be, use [`method: Locator.or`] to create a locator that matches any one or both of the alternatives.
|
||||
If you'd like to target one of the two or more elements, and you don't know which one it will be, use [`method: Locator.or`] to create a locator that matches all of the alternatives.
|
||||
|
||||
For example, consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly.
|
||||
|
||||
|
|
@ -1310,19 +1310,19 @@ Consider a page with two buttons, the first invisible and the second [visible](.
|
|||
* This will only find a second button, because it is visible, and then click it.
|
||||
|
||||
```js
|
||||
await page.locator('button').filter({ visible: true }).click();
|
||||
await page.locator('button').locator('visible=true').click();
|
||||
```
|
||||
```java
|
||||
page.locator("button").filter(new Locator.FilterOptions.setVisible(true)).click();
|
||||
page.locator("button").locator("visible=true").click();
|
||||
```
|
||||
```python async
|
||||
await page.locator("button").filter(visible=True).click()
|
||||
await page.locator("button").locator("visible=true").click()
|
||||
```
|
||||
```python sync
|
||||
page.locator("button").filter(visible=True).click()
|
||||
page.locator("button").locator("visible=true").click()
|
||||
```
|
||||
```csharp
|
||||
await page.Locator("button").Filter(new() { Visible = true }).ClickAsync();
|
||||
await page.Locator("button").Locator("visible=true").ClickAsync();
|
||||
```
|
||||
|
||||
## Lists
|
||||
|
|
@ -1356,7 +1356,7 @@ expect(page.get_by_role("listitem")).to_have_count(3)
|
|||
```
|
||||
|
||||
```java
|
||||
assertThat(page.getByRole(AriaRole.LISTITEM)).hasCount(3);
|
||||
assertThat(page.getByRole(AriaRole.LISTITEM).hasCount(3);
|
||||
```
|
||||
|
||||
```csharp
|
||||
|
|
|
|||
132
docs/src/mock.md
132
docs/src/mock.md
|
|
@ -195,15 +195,15 @@ await Expect(page.GetByTextAsync("Loquat", new () { Exact = true })).ToBeVisible
|
|||
page.route("*/**/api/v1/fruits", route -> {
|
||||
Response response = route.fetch();
|
||||
byte[] json = response.body();
|
||||
JsonObject parsed = new Gson().fromJson(new String(json), JsonObject.class);
|
||||
parsed = new Gson().fromJson(json, JsonObject.class)
|
||||
parsed.add(new JsonObject().add("name", "Loquat").add("id", 100));
|
||||
// Fulfill using the original response, while patching the response body
|
||||
// with the given JSON object.
|
||||
route.fulfill(new Route.FulfillOptions().setResponse(response).setBody(parsed.toString()));
|
||||
route.fulfill(new Route.FulfillOptions().setResponse(response).setBody(json.toString()));
|
||||
});
|
||||
|
||||
// Go to the page
|
||||
page.navigate("https://demo.playwright.dev/api-mocking");
|
||||
page.goto("https://demo.playwright.dev/api-mocking");
|
||||
|
||||
// Assert that the Loquat fruit is visible
|
||||
assertThat(page.getByText("Loquat", new Page.GetByTextOptions().setExact(true))).isVisible();
|
||||
|
|
@ -294,7 +294,7 @@ page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions()
|
|||
);
|
||||
|
||||
// Go to the page
|
||||
page.navigate("https://demo.playwright.dev/api-mocking");
|
||||
page.goto("https://demo.playwright.dev/api-mocking");
|
||||
|
||||
// Assert that the fruit is visible
|
||||
assertThat(page.getByText("Strawberry")).isVisible();
|
||||
|
|
@ -392,11 +392,10 @@ page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions()
|
|||
);
|
||||
|
||||
// Go to the page
|
||||
page.navigate("https://demo.playwright.dev/api-mocking");
|
||||
page.goto("https://demo.playwright.dev/api-mocking");
|
||||
|
||||
// Assert that the Playwright fruit is visible
|
||||
assertThat(page.getByText("Playwright", new Page.GetByTextOptions()
|
||||
.setExact(true))).isVisible();
|
||||
assertThat(page.getByText("Playwright", new Page.GetByTextOptions().setExact(true))).isVisible();
|
||||
```
|
||||
In the trace of our test we can see that the route was fulfilled from the HAR file and the API was not called.
|
||||

|
||||
|
|
@ -435,122 +434,3 @@ pwsh bin/Debug/netX/playwright.ps1 open --save-har=example.har --save-har-glob="
|
|||
```
|
||||
|
||||
Read more about [advanced networking](./network.md).
|
||||
|
||||
## Mock WebSockets
|
||||
|
||||
The following code will intercept WebSocket connections and mock entire communcation over the WebSocket, instead of connecting to the server. This example responds to a `"request"` with a `"response"`.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('wss://example.com/ws', ws => {
|
||||
ws.onMessage(message => {
|
||||
if (message === 'request')
|
||||
ws.send('response');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("wss://example.com/ws", ws -> {
|
||||
ws.onMessage(frame -> {
|
||||
if ("request".equals(frame.text()))
|
||||
ws.send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
await page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text == "request")
|
||||
ws.Send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, you may want to connect to the actual server, but intercept messages in-between and modify or block them. Here is an example that modifies some of the messages sent by the page to the server, and leaves the rest unmodified.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('wss://example.com/ws', ws => {
|
||||
const server = ws.connectToServer();
|
||||
ws.onMessage(message => {
|
||||
if (message === 'request')
|
||||
server.send('request2');
|
||||
else
|
||||
server.send(message);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```java
|
||||
page.routeWebSocket("wss://example.com/ws", ws -> {
|
||||
WebSocketRoute server = ws.connectToServer();
|
||||
ws.onMessage(frame -> {
|
||||
if ("request".equals(frame.text()))
|
||||
server.send("request2");
|
||||
else
|
||||
server.send(frame.text());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python async
|
||||
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
server.send("request2")
|
||||
else:
|
||||
server.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
server = ws.connect_to_server()
|
||||
ws.on_message(lambda message: message_handler(server, message))
|
||||
|
||||
await page.route_web_socket("wss://example.com/ws", handler)
|
||||
```
|
||||
|
||||
```python sync
|
||||
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
server.send("request2")
|
||||
else:
|
||||
server.send(message)
|
||||
|
||||
def handler(ws: WebSocketRoute):
|
||||
server = ws.connect_to_server()
|
||||
ws.on_message(lambda message: message_handler(server, message))
|
||||
|
||||
page.route_web_socket("wss://example.com/ws", handler)
|
||||
```
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
|
||||
var server = ws.ConnectToServer();
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text == "request")
|
||||
server.Send("request2");
|
||||
else
|
||||
server.Send(frame.Text);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
For more details, see [WebSocketRoute].
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ await page.GotoAsync("https://example.com");
|
|||
You can configure pages to load over the HTTP(S) proxy or SOCKSv5. Proxy can be either set globally
|
||||
for the entire browser, or for each browser context individually.
|
||||
|
||||
You can optionally specify username and password for HTTP(S) proxy, you can also specify hosts to bypass the [`option: Browser.newContext.proxy`] for.
|
||||
You can optionally specify username and password for HTTP(S) proxy, you can also specify hosts to
|
||||
bypass proxy for.
|
||||
|
||||
Here is an example of a global proxy:
|
||||
|
||||
|
|
@ -145,8 +146,8 @@ const browser = await chromium.launch({
|
|||
```java
|
||||
Browser browser = chromium.launch(new BrowserType.LaunchOptions()
|
||||
.setProxy(new Proxy("http://myproxy.com:3128")
|
||||
.setUsername("usr")
|
||||
.setPassword("pwd")));
|
||||
.setUsername('usr')
|
||||
.setPassword('pwd')));
|
||||
```
|
||||
|
||||
```python async
|
||||
|
|
@ -626,7 +627,7 @@ page.route("**/title.html", route -> {
|
|||
String body = response.text();
|
||||
body = body.replace("<title>", "<title>My prefix:");
|
||||
Map<String, String> headers = response.headers();
|
||||
headers.put("content-type", "text/html");
|
||||
headers.put("content-type": "text/html");
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
// Pass all fields from the response.
|
||||
.setResponse(response)
|
||||
|
|
@ -699,36 +700,9 @@ await Page.RouteAsync("**/title.html", async route =>
|
|||
});
|
||||
```
|
||||
|
||||
## Glob URL patterns
|
||||
|
||||
Playwright uses simplified glob patterns for URL matching in network interception methods like [`method: Page.route`] or [`method: Page.waitForResponse`]. These patterns support basic wildcards:
|
||||
|
||||
1. Asterisks:
|
||||
- A single `*` matches any characters except `/`
|
||||
- A double `**` matches any characters including `/`
|
||||
1. Question mark `?` matches any single character except `/`
|
||||
1. Curly braces `{}` can be used to match a list of options separated by commas `,`
|
||||
1. Square brackets `[]` can be used to match a set of characters
|
||||
1. Backslash `\` can be used to escape any of special characters (note to escape backslash itself as `\\`)
|
||||
|
||||
Examples:
|
||||
- `https://example.com/*.js` matches `https://example.com/file.js` but not `https://example.com/path/file.js`
|
||||
- `https://example.com/\\?page=1` matches `https://example.com/?page=1` but not `https://example.com`
|
||||
- `**/v[0-9]*` matches `https://example.com/v1/` but not `https://example.com/vote/`
|
||||
- `**/*.js` matches both `https://example.com/file.js` and `https://example.com/path/file.js`
|
||||
- `**/*.{png,jpg,jpeg}` matches all image requests
|
||||
|
||||
Important notes:
|
||||
|
||||
- The glob pattern must match the entire URL, not just a part of it.
|
||||
- When using globs for URL matching, consider the full URL structure, including the protocol and path separators.
|
||||
- For more complex matching requirements, consider using [RegExp] instead of glob patterns.
|
||||
|
||||
## WebSockets
|
||||
|
||||
Playwright supports [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) inspection, mocking and modifying out of the box. See our [API mocking guide](./mock.md#mock-websockets) to learn how to mock WebSockets.
|
||||
|
||||
Every time a WebSocket is created, the [`event: Page.webSocket`] event is fired. This event contains the [WebSocket] instance for further web socket frames inspection:
|
||||
Playwright supports [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) inspection out of the box. Every time a WebSocket is created, the [`event: Page.webSocket`] event is fired. This event contains the [WebSocket] instance for further web socket frames inspection:
|
||||
|
||||
```js
|
||||
page.on('websocket', ws => {
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ Page objects can then be used inside a test.
|
|||
```java
|
||||
import models.SearchPage;
|
||||
import com.microsoft.playwright.*;
|
||||
// ...
|
||||
...
|
||||
|
||||
// In the test
|
||||
Page page = browser.newPage();
|
||||
|
|
|
|||
|
|
@ -4,163 +4,6 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.50
|
||||
|
||||
### Support for Xunit
|
||||
|
||||
* Support for xUnit 2.8+ via [Microsoft.Playwright.Xunit](https://www.nuget.org/packages/Microsoft.Playwright.Xunit). Follow our [Getting Started](./intro.md) guide to learn more.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Added method [`method: LocatorAssertions.toHaveAccessibleErrorMessage`] to assert the Locator points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage).
|
||||
|
||||
### UI updates
|
||||
|
||||
* New button in Codegen for picking elements to produce aria snapshots.
|
||||
* Additional details (such as keys pressed) are now displayed alongside action API calls in traces.
|
||||
* Display of `canvas` content in traces is error-prone. Display is now disabled by default, and can be enabled via the `Display canvas content` UI setting.
|
||||
* `Call` and `Network` panels now display additional time information.
|
||||
|
||||
### Breaking
|
||||
|
||||
* [`method: LocatorAssertions.toBeEditable`] and [`method: Locator.isEditable`] now throw if the target element is not `<input>`, `<select>`, or a number of other editable elements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 133.0.6943.16
|
||||
* Mozilla Firefox 134.0
|
||||
* WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 132
|
||||
* Microsoft Edge 132
|
||||
|
||||
## Version 1.49
|
||||
|
||||
### Aria snapshots
|
||||
|
||||
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
|
||||
|
||||
```csharp
|
||||
await page.GotoAsync("https://playwright.dev");
|
||||
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"
|
||||
- banner:
|
||||
- heading /Playwright enables reliable/ [level=1]
|
||||
- link ""Get started""
|
||||
- link ""Star microsoft/playwright on GitHub""
|
||||
- main:
|
||||
- img ""Browsers (Chromium, Firefox, WebKit)""
|
||||
- heading ""Any browser • Any platform • One API""
|
||||
");
|
||||
```
|
||||
|
||||
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
|
||||
|
||||
Learn more in the [aria snapshots guide](./aria-snapshots).
|
||||
|
||||
### Tracing groups
|
||||
|
||||
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
|
||||
|
||||
```csharp
|
||||
// All actions between GroupAsync and GroupEndAsync
|
||||
// will be shown in the trace viewer as a group.
|
||||
await Page.Context.Tracing.GroupAsync("Open Playwright.dev > API");
|
||||
await Page.GotoAsync("https://playwright.dev/");
|
||||
await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();
|
||||
await Page.Context.Tracing.GroupEndAsync();
|
||||
```
|
||||
|
||||
### Breaking: `chrome` and `msedge` channels switch to new headless mode
|
||||
|
||||
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
|
||||
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
|
||||
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
|
||||
|
||||
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
|
||||
|
||||
### Try new Chromium headless
|
||||
|
||||
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
|
||||
|
||||
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
|
||||
|
||||
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
|
||||
|
||||
```xml csharp title="runsettings.xml"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<Playwright>
|
||||
<BrowserName>chromium</BrowserName>
|
||||
<LaunchOptions>
|
||||
<Channel>chromium</Channel>
|
||||
</LaunchOptions>
|
||||
</Playwright>
|
||||
</RunSettings>
|
||||
```
|
||||
|
||||
```bash csharp
|
||||
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=chromium
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
|
||||
- `<canvas>` elements inside a snapshot now draw a preview.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 131.0.6778.33
|
||||
- Mozilla Firefox 132.0
|
||||
- WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 130
|
||||
- Microsoft Edge 130
|
||||
|
||||
|
||||
## Version 1.48
|
||||
|
||||
### WebSocket routing
|
||||
|
||||
New methods [`method: Page.routeWebSocket`] and [`method: BrowserContext.routeWebSocket`] allow to intercept, modify and mock WebSocket connections initiated in the page. Below is a simple example that mocks WebSocket communication by responding to a `"request"` with a `"response"`.
|
||||
|
||||
```csharp
|
||||
await page.RouteWebSocketAsync("/ws", ws => {
|
||||
ws.OnMessage(frame => {
|
||||
if (frame.Text == "request")
|
||||
ws.Send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
See [WebSocketRoute] for more details.
|
||||
|
||||
### UI updates
|
||||
|
||||
- New "copy" buttons for annotations and test location in the HTML report.
|
||||
- Route method calls like [`method: Route.fulfill`] are not shown in the report and trace viewer anymore. You can see which network requests were routed in the network tab instead.
|
||||
- New "Copy as cURL" and "Copy as fetch" buttons for requests in the network tab.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New method [`method: Page.requestGC`] may help detect memory leaks.
|
||||
- Requests made by [APIRequestContext] now record detailed timing and security information in the HAR.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 130.0.6723.19
|
||||
- Mozilla Firefox 130.0
|
||||
- WebKit 18.0
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 129
|
||||
- Microsoft Edge 129
|
||||
|
||||
|
||||
## Version 1.47
|
||||
|
||||
### Network Tab improvements
|
||||
|
|
@ -178,8 +21,8 @@ The Network tab in the trace viewer has several nice improvements:
|
|||
- The `mcr.microsoft.com/playwright/dotnet:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright/dotnet:v1.47.0-jammy` instead.
|
||||
- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as byte arrays instead of file paths.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as byte arrays instead of file paths.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -441,7 +284,7 @@ await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Light and easy" })
|
|||
|
||||
### New APIs
|
||||
|
||||
- [`method: Page.pdf`] accepts two new options [`option: Page.pdf.tagged`] and [`option: Page.pdf.outline`].
|
||||
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||
|
||||
### Announcements
|
||||
|
||||
|
|
@ -464,7 +307,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -502,8 +345,8 @@ await Expect(Page.GetByPlaceholder("Search docs")).ToHaveValueAsync("locator");
|
|||
|
||||
### New APIs
|
||||
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -674,7 +517,7 @@ This version was also tested against the following stable channels:
|
|||
await Page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
|
||||
await newEmail.ClickAsync();
|
||||
```
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```csharp
|
||||
|
|
@ -691,10 +534,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
|
||||
### ⚠️ Breaking change
|
||||
|
||||
|
|
@ -717,9 +560,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
### New APIs
|
||||
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -824,9 +667,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
@ -904,7 +747,7 @@ All the same methods are also available on [Locator], [FrameLocator] and [Frame]
|
|||
- [`method: LocatorAssertions.toHaveAttribute`] with an empty value does not match missing attribute anymore. For example, the following snippet will succeed when `button` **does not** have a `disabled` attribute.
|
||||
|
||||
```csharp
|
||||
await Expect(Page.GetByRole(AriaRole.Button)).ToHaveAttributeAsync("disabled", "");
|
||||
await Expect(Page.GetByRole(AriaRole.Button)).ToHaveAttribute("disabled", "");
|
||||
```
|
||||
|
||||
### Browser Versions
|
||||
|
|
|
|||
|
|
@ -4,147 +4,6 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.50
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Added method [`method: LocatorAssertions.toHaveAccessibleErrorMessage`] to assert the Locator points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage).
|
||||
|
||||
### UI updates
|
||||
|
||||
* New button in Codegen for picking elements to produce aria snapshots.
|
||||
* Additional details (such as keys pressed) are now displayed alongside action API calls in traces.
|
||||
* Display of `canvas` content in traces is error-prone. Display is now disabled by default, and can be enabled via the `Display canvas content` UI setting.
|
||||
* `Call` and `Network` panels now display additional time information.
|
||||
|
||||
### Breaking
|
||||
|
||||
* [`method: LocatorAssertions.toBeEditable`] and [`method: Locator.isEditable`] now throw if the target element is not `<input>`, `<select>`, or a number of other editable elements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 133.0.6943.16
|
||||
* Mozilla Firefox 134.0
|
||||
* WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 132
|
||||
* Microsoft Edge 132
|
||||
|
||||
## Version 1.49
|
||||
|
||||
### Aria snapshots
|
||||
|
||||
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
|
||||
|
||||
```java
|
||||
page.navigate("https://playwright.dev");
|
||||
assertThat(page.locator("body")).matchesAriaSnapshot("""
|
||||
- banner:
|
||||
- heading /Playwright enables reliable/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- main:
|
||||
- img "Browsers (Chromium, Firefox, WebKit)"
|
||||
- heading "Any browser • Any platform • One API"
|
||||
""");
|
||||
```
|
||||
|
||||
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
|
||||
|
||||
Learn more in the [aria snapshots guide](./aria-snapshots).
|
||||
|
||||
### Tracing groups
|
||||
|
||||
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
|
||||
|
||||
```java
|
||||
// All actions between group and groupEnd
|
||||
// will be shown in the trace viewer as a group.
|
||||
page.context().tracing().group("Open Playwright.dev > API");
|
||||
page.navigate("https://playwright.dev/");
|
||||
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
|
||||
page.context().tracing().groupEnd();
|
||||
```
|
||||
|
||||
### Breaking: `chrome` and `msedge` channels switch to new headless mode
|
||||
|
||||
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
|
||||
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
|
||||
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
|
||||
|
||||
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
|
||||
|
||||
### Try new Chromium headless
|
||||
|
||||
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
|
||||
|
||||
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
|
||||
|
||||
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
|
||||
|
||||
```java
|
||||
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setChannel("chromium"));
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
|
||||
- `<canvas>` elements inside a snapshot now draw a preview.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 131.0.6778.33
|
||||
- Mozilla Firefox 132.0
|
||||
- WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 130
|
||||
- Microsoft Edge 130
|
||||
|
||||
|
||||
## Version 1.48
|
||||
|
||||
### WebSocket routing
|
||||
|
||||
New methods [`method: Page.routeWebSocket`] and [`method: BrowserContext.routeWebSocket`] allow to intercept, modify and mock WebSocket connections initiated in the page. Below is a simple example that mocks WebSocket communication by responding to a `"request"` with a `"response"`.
|
||||
|
||||
```java
|
||||
page.routeWebSocket("/ws", ws -> {
|
||||
ws.onMessage(frame -> {
|
||||
if ("request".equals(frame.text()))
|
||||
ws.send("response");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
See [WebSocketRoute] for more details.
|
||||
|
||||
### UI updates
|
||||
|
||||
- New "copy" buttons for annotations and test location in the HTML report.
|
||||
- Route method calls like [`method: Route.fulfill`] are not shown in the report and trace viewer anymore. You can see which network requests were routed in the network tab instead.
|
||||
- New "Copy as cURL" and "Copy as fetch" buttons for requests in the network tab.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New method [`method: Page.requestGC`] may help detect memory leaks.
|
||||
- Requests made by [APIRequestContext] now record detailed timing and security information in the HAR.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 130.0.6723.19
|
||||
- Mozilla Firefox 130.0
|
||||
- WebKit 18.0
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 129
|
||||
- Microsoft Edge 129
|
||||
|
||||
|
||||
## Version 1.47
|
||||
|
||||
### Network Tab improvements
|
||||
|
|
@ -162,8 +21,8 @@ The Network tab in the trace viewer has several nice improvements:
|
|||
- The `mcr.microsoft.com/playwright/java:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.02 jammy-based image, please use `mcr.microsoft.com/playwright/java:v1.47.0-jammy` instead.
|
||||
- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as byte arrays instead of file paths.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as byte arrays instead of file paths.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -490,7 +349,7 @@ assertThat(page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setName(
|
|||
|
||||
### New APIs
|
||||
|
||||
- [`method: Page.pdf`] accepts two new options [`option: Page.pdf.tagged`] and [`option: Page.pdf.outline`].
|
||||
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||
|
||||
### Announcements
|
||||
|
||||
|
|
@ -513,7 +372,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`].
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -551,8 +410,8 @@ assertThat(page.getByPlaceholder("Search docs")).hasValue("locator");
|
|||
|
||||
### New APIs
|
||||
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -738,7 +597,7 @@ This version was also tested against the following stable channels:
|
|||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
|
||||
newEmail.click();
|
||||
```
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```java
|
||||
|
|
@ -757,10 +616,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
|
||||
### Other highlights
|
||||
|
||||
|
|
@ -787,9 +646,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
### New APIs
|
||||
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -888,9 +747,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
@ -1288,12 +1147,14 @@ Playwright for Java 1.18 introduces [Web-First Assertions](./test-assertions).
|
|||
Consider the following example:
|
||||
|
||||
```java
|
||||
...
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
|
||||
public class TestExample {
|
||||
...
|
||||
@Test
|
||||
void statusBecomesSubmitted() {
|
||||
// ...
|
||||
...
|
||||
page.locator("#submit-button").click();
|
||||
assertThat(page.locator(".status")).hasText("Submitted");
|
||||
}
|
||||
|
|
@ -1570,19 +1431,19 @@ button.click("button >> visible=true");
|
|||
Traces are recorded using the new [`property: BrowserContext.tracing`] API:
|
||||
|
||||
```java
|
||||
Browser browser = playwright.chromium().launch();
|
||||
Browser browser = chromium.launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
|
||||
// Start tracing before creating / navigating a page.
|
||||
context.tracing().start(new Tracing.StartOptions()
|
||||
context.tracing.start(new Tracing.StartOptions()
|
||||
.setScreenshots(true)
|
||||
.setSnapshots(true));
|
||||
.setSnapshots(true);
|
||||
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://playwright.dev");
|
||||
page.goto("https://playwright.dev");
|
||||
|
||||
// Stop tracing and export it into a zip archive.
|
||||
context.tracing().stop(new Tracing.StopOptions()
|
||||
context.tracing.stop(new Tracing.StopOptions()
|
||||
.setPath(Paths.get("trace.zip")));
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -6,214 +6,6 @@ toc_max_heading_level: 2
|
|||
|
||||
import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||
|
||||
## Version 1.50
|
||||
|
||||
### Test runner
|
||||
|
||||
* New option [`option: Test.step.timeout`] allows specifying a maximum run time for an individual test step. A timed-out step will fail the execution of the test.
|
||||
|
||||
```js
|
||||
test('some test', async ({ page }) => {
|
||||
await test.step('a step', async () => {
|
||||
// This step can time out separately from the test
|
||||
}, { timeout: 1000 });
|
||||
});
|
||||
```
|
||||
|
||||
* New method [`method: Test.step.skip`] to disable execution of a test step.
|
||||
|
||||
```js
|
||||
test('some test', async ({ page }) => {
|
||||
await test.step('before running step', async () => {
|
||||
// Normal step
|
||||
});
|
||||
|
||||
await test.step.skip('not yet ready', async () => {
|
||||
// This step is skipped
|
||||
});
|
||||
|
||||
await test.step('after running step', async () => {
|
||||
// This step still runs even though the previous one was skipped
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
* Expanded [`method: LocatorAssertions.toMatchAriaSnapshot#2`] to allow storing of aria snapshots in separate YAML files.
|
||||
* Added method [`method: LocatorAssertions.toHaveAccessibleErrorMessage`] to assert the Locator points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage).
|
||||
* Option [`property: TestConfig.updateSnapshots`] added the configuration enum `changed`. `changed` updates only the snapshots that have changed, whereas `all` now updates all snapshots, regardless of whether there are any differences.
|
||||
* New option [`property: TestConfig.updateSourceMethod`] defines the way source code is updated when [`property: TestConfig.updateSnapshots`] is configured. Added `overwrite` and `3-way` modes that write the changes into source code, on top of existing `patch` mode that creates a patch file.
|
||||
|
||||
```bash
|
||||
npx playwright test --update-snapshots=changed --update-source-method=3way
|
||||
```
|
||||
|
||||
* Option [`property: TestConfig.webServer`] added a `gracefulShutdown` field for specifying a process kill signal other than the default `SIGKILL`.
|
||||
* Exposed [`property: TestStep.attachments`] from the reporter API to allow retrieval of all attachments created by that step.
|
||||
* New option `pathTemplate` for `toHaveScreenshot` and `toMatchAriaSnapshot` assertions in the [`property: TestConfig.expect`] configuration.
|
||||
|
||||
### UI updates
|
||||
|
||||
* Updated default HTML reporter to improve display of attachments.
|
||||
* New button in Codegen for picking elements to produce aria snapshots.
|
||||
* Additional details (such as keys pressed) are now displayed alongside action API calls in traces.
|
||||
* Display of `canvas` content in traces is error-prone. Display is now disabled by default, and can be enabled via the `Display canvas content` UI setting.
|
||||
* `Call` and `Network` panels now display additional time information.
|
||||
|
||||
### Breaking
|
||||
|
||||
* [`method: LocatorAssertions.toBeEditable`] and [`method: Locator.isEditable`] now throw if the target element is not `<input>`, `<select>`, or a number of other editable elements.
|
||||
* Option [`property: TestConfig.updateSnapshots`] now updates all snapshots when set to `all`, rather than only the failed/changed snapshots. Use the new enum `changed` to keep the old functionality of only updating the changed snapshots.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 133.0.6943.16
|
||||
* Mozilla Firefox 134.0
|
||||
* WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 132
|
||||
* Microsoft Edge 132
|
||||
|
||||
## Version 1.49
|
||||
|
||||
<LiteYouTube
|
||||
id="S5wCft-ImKk"
|
||||
title="Playwright 1.49"
|
||||
/>
|
||||
|
||||
### Aria snapshots
|
||||
|
||||
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
|
||||
|
||||
```js
|
||||
await page.goto('https://playwright.dev');
|
||||
await expect(page.locator('body')).toMatchAriaSnapshot(`
|
||||
- banner:
|
||||
- heading /Playwright enables reliable/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- main:
|
||||
- img "Browsers (Chromium, Firefox, WebKit)"
|
||||
- heading "Any browser • Any platform • One API"
|
||||
`);
|
||||
```
|
||||
|
||||
You can generate this assertion with [Test Generator](./codegen) and update the expected snapshot with `--update-snapshots` command line flag.
|
||||
|
||||
Learn more in the [aria snapshots guide](./aria-snapshots).
|
||||
|
||||
### Test runner
|
||||
|
||||
- New option [`property: TestConfig.tsconfig`] allows to specify a single `tsconfig` to be used for all tests.
|
||||
- New method [`method: Test.fail.only`] to focus on a failing test.
|
||||
- Options [`property: TestConfig.globalSetup`] and [`property: TestConfig.globalTeardown`] now support multiple setups/teardowns.
|
||||
- New value `'on-first-failure'` for [`property: TestOptions.screenshot`].
|
||||
- Added "previous" and "next" buttons to the HTML report to quickly switch between test cases.
|
||||
- New properties [`property: TestInfoError.cause`] and [`property: TestError.cause`] mirroring [`Error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause).
|
||||
|
||||
### Breaking: `chrome` and `msedge` channels switch to new headless mode
|
||||
|
||||
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
|
||||
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
|
||||
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
|
||||
|
||||
#### What do I need to do?
|
||||
|
||||
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
|
||||
|
||||
### Other breaking changes
|
||||
|
||||
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
|
||||
- Package `@playwright/experimental-ct-vue2` will no longer be updated.
|
||||
- Package `@playwright/experimental-ct-solid` will no longer be updated.
|
||||
|
||||
### Try new Chromium headless
|
||||
|
||||
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
|
||||
|
||||
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
|
||||
|
||||
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
|
||||
|
||||
```js
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chromium' },
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- `<canvas>` elements inside a snapshot now draw a preview.
|
||||
- New method [`method: Tracing.group`] to visually group actions in the trace.
|
||||
- Playwright docker images switched from Node.js v20 to Node.js v22 LTS.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 131.0.6778.33
|
||||
- Mozilla Firefox 132.0
|
||||
- WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 130
|
||||
- Microsoft Edge 130
|
||||
|
||||
|
||||
## Version 1.48
|
||||
|
||||
<LiteYouTube
|
||||
id="VGlkSBkMVCQ"
|
||||
title="Playwright 1.48"
|
||||
/>
|
||||
|
||||
### WebSocket routing
|
||||
|
||||
New methods [`method: Page.routeWebSocket`] and [`method: BrowserContext.routeWebSocket`] allow to intercept, modify and mock WebSocket connections initiated in the page. Below is a simple example that mocks WebSocket communication by responding to a `"request"` with a `"response"`.
|
||||
|
||||
```js
|
||||
await page.routeWebSocket('/ws', ws => {
|
||||
ws.onMessage(message => {
|
||||
if (message === 'request')
|
||||
ws.send('response');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
See [WebSocketRoute] for more details.
|
||||
|
||||
### UI updates
|
||||
|
||||
- New "copy" buttons for annotations and test location in the HTML report.
|
||||
- Route method calls like [`method: Route.fulfill`] are not shown in the report and trace viewer anymore. You can see which network requests were routed in the network tab instead.
|
||||
- New "Copy as cURL" and "Copy as fetch" buttons for requests in the network tab.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Option [`option: APIRequestContext.fetch.form`] and similar ones now accept [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
|
||||
- New method [`method: Page.requestGC`] may help detect memory leaks.
|
||||
- New option [`option: Test.step.location`] to pass custom step location.
|
||||
- Requests made by [APIRequestContext] now record detailed timing and security information in the HAR.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 130.0.6723.19
|
||||
- Mozilla Firefox 130.0
|
||||
- WebKit 18.0
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 129
|
||||
- Microsoft Edge 129
|
||||
|
||||
|
||||
## Version 1.47
|
||||
|
||||
### Network Tab improvements
|
||||
|
|
@ -258,10 +50,10 @@ test('query params', async ({ request }) => {
|
|||
|
||||
- The `mcr.microsoft.com/playwright:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright:v1.47.0-jammy` instead.
|
||||
- New options [`option: Page.removeAllListeners.behavior`], [`option: Browser.removeAllListeners.behavior`] and [`option: BrowserContext.removeAllListeners.behavior`] to wait for ongoing listeners to complete.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as buffers instead of file paths.
|
||||
- New option [`option: behavior`] in [`method: Page.removeAllListeners`], [`method: Browser.removeAllListeners`] and [`method: BrowserContext.removeAllListeners`] to wait for ongoing listeners to complete.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as buffers instead of file paths.
|
||||
- Attachments with a `text/html` content type can now be opened in a new tab in the HTML report. This is useful for including third-party reports or other HTML content in the Playwright test report and distributing it to your team.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] option in [`method: Locator.selectOption`] was deprecated.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -736,7 +528,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New option `stylePath` for methods [`method: PageAssertions.toHaveScreenshot#1`] and [`method: LocatorAssertions.toHaveScreenshot#1`] to apply a custom stylesheet while making the screenshot.
|
||||
- New `fileName` option for [Blob reporter](./test-reporters#blob-reporter), to specify the name of the report to be created.
|
||||
|
||||
|
|
@ -785,8 +577,8 @@ test('test', async ({ page }) => {
|
|||
|
||||
### New APIs
|
||||
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -1255,7 +1047,7 @@ This version was also tested against the following stable channels:
|
|||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await newEmail.click();
|
||||
```
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```js
|
||||
|
|
@ -1272,10 +1064,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
- [`method: Reporter.onExit`]
|
||||
|
||||
### ⚠️ Breaking change
|
||||
|
|
@ -1315,10 +1107,10 @@ npx playwright test --ui
|
|||
|
||||
### New APIs
|
||||
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New property [`property: TestInfo.testId`].
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
|
||||
### ⚠️ Breaking change in component tests
|
||||
|
|
@ -1498,9 +1290,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -4,153 +4,6 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.50
|
||||
|
||||
### Async Pytest Plugin
|
||||
|
||||
* [Playwright's Pytest plugin](./test-runners.md) now has support for [Async Fixtures](https://playwright.dev/python/docs/test-runners#async-fixtures).
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* Added method [`method: LocatorAssertions.toHaveAccessibleErrorMessage`] to assert the Locator points to an element with a given [aria errormessage](https://w3c.github.io/aria/#aria-errormessage).
|
||||
|
||||
### UI updates
|
||||
|
||||
* New button in Codegen for picking elements to produce aria snapshots.
|
||||
* Additional details (such as keys pressed) are now displayed alongside action API calls in traces.
|
||||
* Display of `canvas` content in traces is error-prone. Display is now disabled by default, and can be enabled via the `Display canvas content` UI setting.
|
||||
* `Call` and `Network` panels now display additional time information.
|
||||
|
||||
### Breaking
|
||||
|
||||
* [`method: LocatorAssertions.toBeEditable`] and [`method: Locator.isEditable`] now throw if the target element is not `<input>`, `<select>`, or a number of other editable elements.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 133.0.6943.16
|
||||
* Mozilla Firefox 134.0
|
||||
* WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 132
|
||||
* Microsoft Edge 132
|
||||
|
||||
## Version 1.49
|
||||
|
||||
### Aria snapshots
|
||||
|
||||
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
|
||||
|
||||
```python
|
||||
page.goto("https://playwright.dev")
|
||||
expect(page.locator('body')).to_match_aria_snapshot('''
|
||||
- banner:
|
||||
- heading /Playwright enables reliable/ [level=1]
|
||||
- link "Get started"
|
||||
- link "Star microsoft/playwright on GitHub"
|
||||
- main:
|
||||
- img "Browsers (Chromium, Firefox, WebKit)"
|
||||
- heading "Any browser • Any platform • One API"
|
||||
''')
|
||||
```
|
||||
|
||||
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
|
||||
|
||||
Learn more in the [aria snapshots guide](./aria-snapshots).
|
||||
|
||||
### Tracing groups
|
||||
|
||||
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
|
||||
|
||||
```python
|
||||
# All actions between group and group_end
|
||||
# will be shown in the trace viewer as a group.
|
||||
page.context.tracing.group("Open Playwright.dev > API")
|
||||
page.goto("https://playwright.dev/")
|
||||
page.get_by_role("link", name="API").click()
|
||||
page.context.tracing.group_end()
|
||||
```
|
||||
|
||||
### Breaking: `chrome` and `msedge` channels switch to new headless mode
|
||||
|
||||
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
|
||||
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
|
||||
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
|
||||
|
||||
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
|
||||
|
||||
### Try new Chromium headless
|
||||
|
||||
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
|
||||
|
||||
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
|
||||
|
||||
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
|
||||
|
||||
```bash python
|
||||
pytest test_login.py --browser-channel chromium
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
|
||||
- `<canvas>` elements inside a snapshot now draw a preview.
|
||||
- Python 3.8 is not supported anymore.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 131.0.6778.33
|
||||
- Mozilla Firefox 132.0
|
||||
- WebKit 18.2
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 130
|
||||
- Microsoft Edge 130
|
||||
|
||||
|
||||
## Version 1.48
|
||||
|
||||
### WebSocket routing
|
||||
|
||||
New methods [`method: Page.routeWebSocket`] and [`method: BrowserContext.routeWebSocket`] allow to intercept, modify and mock WebSocket connections initiated in the page. Below is a simple example that mocks WebSocket communication by responding to a `"request"` with a `"response"`.
|
||||
|
||||
```python
|
||||
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
|
||||
if message == "request":
|
||||
ws.send("response")
|
||||
|
||||
page.route_web_socket("/ws", lambda ws: ws.on_message(
|
||||
lambda message: message_handler(ws, message)
|
||||
))
|
||||
```
|
||||
|
||||
See [WebSocketRoute] for more details.
|
||||
|
||||
### UI updates
|
||||
|
||||
- New "copy" buttons for annotations and test location in the HTML report.
|
||||
- Route method calls like [`method: Route.fulfill`] are not shown in the report and trace viewer anymore. You can see which network requests were routed in the network tab instead.
|
||||
- New "Copy as cURL" and "Copy as fetch" buttons for requests in the network tab.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- New method [`method: Page.requestGC`] may help detect memory leaks.
|
||||
- Requests made by [APIRequestContext] now record detailed timing and security information in the HAR.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
- Chromium 130.0.6723.19
|
||||
- Mozilla Firefox 130.0
|
||||
- WebKit 18.0
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
- Google Chrome 129
|
||||
- Microsoft Edge 129
|
||||
|
||||
|
||||
## Version 1.47
|
||||
|
||||
### Network Tab improvements
|
||||
|
|
@ -168,8 +21,8 @@ The Network tab in the trace viewer has several nice improvements:
|
|||
- The `mcr.microsoft.com/playwright/python:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright/python:v1.47.0-jammy` instead.
|
||||
- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as bytes instead of file paths.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as bytes instead of file paths.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -407,7 +260,7 @@ expect(page.get_by_role("heading", name="Light and easy")).to_be_visible()
|
|||
|
||||
### New APIs
|
||||
|
||||
- [`method: Page.pdf`] accepts two new options [`option: Page.pdf.tagged`] and [`option: Page.pdf.outline`].
|
||||
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||
|
||||
### Announcements
|
||||
|
||||
|
|
@ -430,7 +283,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -471,8 +324,8 @@ def test_example(page: Page) -> None:
|
|||
|
||||
### New APIs
|
||||
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -651,7 +504,7 @@ This version was also tested against the following stable channels:
|
|||
page.get_by_role("button", name="Dismiss").click()
|
||||
new_email.click()
|
||||
```
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```python
|
||||
|
|
@ -667,10 +520,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
|
||||
### ⚠️ Breaking change
|
||||
|
||||
|
|
@ -694,9 +547,9 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- Custom expect message, see [test assertions documentation](./test-assertions.md#custom-expect-message).
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -800,9 +653,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
```html
|
||||
<select multiple>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="red">Red</div>
|
||||
<option value="green">Green</div>
|
||||
<option value="blue">Blue</div>
|
||||
</select>
|
||||
```
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue