Compare commits

..

14 commits

Author SHA1 Message Date
Playwright Service 50769b4e23
cherry-pick(#30964): docs: add release video (#30975)
This PR cherry-picks the following commits:

- 7ead708902

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-23 10:33:51 +02:00
Max Schmitt afacb84a52
chore: mark v1.44.1 (#30942) 2024-05-23 09:29:52 +02:00
Dmitry Gozman be133650f6
cherry-pick(#30853): chore: print friendly localhost address from http server (#30881) 2024-05-21 08:40:49 -07:00
Max Schmitt 67b85e6ace
docs: cherry-pick dotnet docs enhancements + release-notes (#30927)
Co-authored-by: Debbie O'Brien <debs-obrien@users.noreply.github.com>
2024-05-21 12:04:31 +02:00
Pavel Feldman 32bde52512 cherry-pick(#30832): chore(testServer): accept video parameter when running tests 2024-05-15 12:47:58 -07:00
Pavel Feldman 5d2623030d cherry-pick(#30807): chore: do not close the reused context when video is on 2024-05-15 12:47:16 -07:00
Playwright Service 3867d5581b
cherry-pick(#30820): fix(electron): allow launching with spaces in path (#30830)
This PR cherry-picks the following commits:

- 90765a226f

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-15 18:46:48 +02:00
Playwright Service 01bf93cda4
cherry-pick(#30800): Revert "fix(highlight): highlight Top Layer elements (#30001)" (#30801)
This PR cherry-picks the following commits:

- b06c1dfff1

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-14 18:00:51 +01:00
Playwright Service 1b2de3f7a7
cherry-pick(#30708): docs(python): roll fixes (#30709)
This PR cherry-picks the following commits:

- 5babb37f19

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-08 18:24:51 +01:00
Yury Semikhatsky a6aa50b0c7
chore: set version to 1.44.0 (#30680) 2024-05-06 11:26:54 -07:00
Yury Semikhatsky 54c157dec5
cherry-pick(#30677): chore: print resolved host in the http server te… (#30679)
…rminal
2024-05-06 11:07:45 -07:00
Playwright Service 2d437e86e2
cherry-pick(#30646): feat(chromium): roll to r1117 (#30652)
This PR cherry-picks the following commits:

- ce69236510

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-03 16:16:44 +00:00
Dmitry Gozman 7637399834
cherry-pick(#30636): fix(role): extract tagName safely (#30639)
Fixes #30616.
2024-05-02 11:19:58 -07:00
Yury Semikhatsky 9e091e74bd
cherry-pick(#30611): chore: add common env vars for junit and json re… (#30624)
…porters
2024-05-01 11:22:08 -07:00
1495 changed files with 36154 additions and 133751 deletions

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

View 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"
},
};

127
.eslintrc.js Normal file
View file

@ -0,0 +1,127 @@
module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "notice"],
parserOptions: {
ecmaVersion: 9,
sourceType: "module",
},
extends: [
"plugin:react-hooks/recommended"
],
/**
* 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
}],
"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"),
}],
}
};

View file

@ -24,7 +24,6 @@ body:
## Make a minimal reproduction ## Make a minimal reproduction
To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the bug. To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the bug.
The simpler you can make it, the more likely we are to successfully verify and fix the bug. You can create a new project with `npm init playwright@latest new-project` and then add the test code there. The simpler you can make it, the more likely we are to successfully verify and fix the bug. You can create a new project with `npm init playwright@latest new-project` and then add the test code there.
Please make sure you only include the code and the dependencies absolutely necessary for your repro. Due to the security considerations, we can only run the code we trust. Major web frameworks are Ok to use, but smaller convenience libraries are not.
- type: markdown - type: markdown
attributes: attributes:
value: | value: |

View file

@ -1,25 +0,0 @@
name: Enable Microphone Access (macOS)
description: 'Allow microphone access to all apps on macOS'
runs:
using: composite
steps:
# https://github.com/actions/runner-images/issues/9330
- name: Allow microphone access to all apps
shell: bash
run: |
if [[ "$(uname)" != "Darwin" ]]; then
echo "Not macOS, exiting"
exit 0
fi
echo "Allowing microphone access to all apps"
version=$(sw_vers -productVersion | cut -d. -f1)
if [[ "$version" == "14" || "$version" == "15" ]]; 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
fi
echo "Successfully allowed microphone access"

View file

@ -1,93 +0,0 @@
name: 'Run browser tests'
description: 'Run browser tests'
inputs:
command:
description: 'Command to run tests'
required: true
node-version:
description: 'Node.js version to use'
required: false
default: '18'
browsers-to-install:
description: 'Browser to install. Default is all browsers.'
required: false
default: ''
bot-name:
description: 'Bot name'
required: true
shell:
description: 'Shell to use'
required: false
default: 'bash'
flakiness-client-id:
description: 'Azure Flakiness Dashboard Client ID'
required: false
flakiness-tenant-id:
description: 'Azure Flakiness Dashboard Tenant ID'
required: false
flakiness-subscription-id:
description: 'Azure Flakiness Dashboard Subscription ID'
required: false
runs:
using: composite
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- uses: ./.github/actions/enable-microphone-access
- run: |
echo "::group::npm ci"
npm ci
echo "::endgroup::"
shell: bash
env:
DEBUG: pw:install
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
- run: |
echo "::group::npm run build"
npm run build
echo "::endgroup::"
shell: bash
- run: |
echo "::group::npx playwright install --with-deps"
npx playwright install --with-deps ${{ inputs.browsers-to-install }}
echo "::endgroup::"
shell: bash
- name: Run tests
if: inputs.shell == 'bash'
run: |
if [[ "$(uname)" == "Linux" ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- ${{ inputs.command }}
else
${{ inputs.command }}
fi
shell: bash
env:
PWTEST_BOT_NAME: ${{ inputs.bot-name }}
- name: Run tests
if: inputs.shell != 'bash'
run: ${{ inputs.command }}
shell: ${{ inputs.shell }}
env:
PWTEST_BOT_NAME: ${{ inputs.bot-name }}
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ inputs.flakiness-client-id }}
tenant-id: ${{ inputs.flakiness-tenant-id }}
subscription-id: ${{ inputs.flakiness-subscription-id }}
- run: |
echo "::group::./utils/upload_flakiness_dashboard.sh"
./utils/upload_flakiness_dashboard.sh ./test-results/report.json
echo "::endgroup::"
if: ${{ !cancelled() }}
shell: bash
- name: Upload blob report
# We only merge reports for PRs as per .github/workflows/create_test_report.yml.
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
uses: ./.github/actions/upload-blob-report
with:
report_dir: blob-report
job_name: ${{ inputs.bot-name }}

View file

@ -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:
- "*"

View file

@ -60,7 +60,7 @@ jobs:
git checkout -b "$BRANCH_NAME" git checkout -b "$BRANCH_NAME"
git push origin $BRANCH_NAME git push origin $BRANCH_NAME
- name: Create Pull Request - name: Create Pull Request
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
script: | script: |

View file

@ -1,7 +1,7 @@
name: Publish Test Results name: Publish Test Results
on: on:
workflow_run: workflow_run:
workflows: ["tests 1", "tests 2", "tests others"] workflows: ["tests 1", "tests 2"]
types: types:
- completed - completed
jobs: jobs:
@ -22,7 +22,6 @@ jobs:
env: env:
DEBUG: pw:install DEBUG: pw:install
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
- run: npm run build - run: npm run build
- name: Download blob report artifact - name: Download blob report artifact
@ -35,7 +34,7 @@ jobs:
run: | run: |
npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports
env: env:
NODE_OPTIONS: --max-old-space-size=8192 NODE_OPTIONS: --max-old-space-size=4096
- name: Azure Login - name: Azure Login
uses: azure/login@v2 uses: azure/login@v2
@ -59,7 +58,7 @@ jobs:
path: '.' path: '.'
- name: Comment on PR - name: Comment on PR
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
script: | script: |
@ -121,3 +120,21 @@ jobs:
]), ]),
}); });
core.info('Posted comment: ' + response.html_url); 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'),
}
});

View file

@ -16,7 +16,7 @@ env:
jobs: jobs:
doc-and-lint: doc-and-lint:
name: "docs & lint" name: "docs & lint"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
@ -35,27 +35,21 @@ jobs:
exit 1 exit 1
fi fi
- name: Audit prod NPM dependencies - name: Audit prod NPM dependencies
run: node utils/check_audit.js run: npm audit --omit dev
lint-snippets: lint-snippets:
name: "Lint snippets" name: "Lint snippets"
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
- uses: actions/setup-python@v5 - uses: actions/setup-python@v4
with: with:
python-version: '3.11' python-version: '3.11'
- uses: actions/setup-dotnet@v4 - uses: actions/setup-dotnet@v3
with: with:
dotnet-version: 8.0.x dotnet-version: 8.0.x
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '21'
- run: npm ci - run: npm ci
- run: pip install -r utils/doclint/linting-code-snippets/python/requirements.txt - 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 - run: node utils/doclint/linting-code-snippets/cli.js

View file

@ -1,4 +1,4 @@
export default { export default {
testDir: '../../tests', testDir: '../../tests',
reporter: [[require.resolve('../../packages/playwright/lib/reporters/markdown')], ['html']] reporter: [['markdown'], ['html']]
}; };

View file

@ -12,31 +12,25 @@ on:
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright' if: github.repository == 'microsoft/playwright'
steps: steps:
- uses: actions/checkout@v4
- name: Create GitHub issue - name: Create GitHub issue
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
script: | script: |
const currentPlaywrightVersion = require('./package.json').version.match(/\d+\.\d+/)[0];
const { data } = await github.rest.git.getCommit({ const { data } = await github.rest.git.getCommit({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
commit_sha: context.sha, commit_sha: context.sha,
}); });
const commitHeader = data.message.split('\n')[0]; 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; const title = '[Ports]: Backport client side changes';
for (const repo of ['playwright-python', 'playwright-java', 'playwright-dotnet']) { for (const repo of ['playwright-python', 'playwright-java', 'playwright-dotnet']) {
const { data: issuesData } = await github.rest.search.issuesAndPullRequests({ const { data: issuesData } = await github.rest.search.issuesAndPullRequests({
q: `is:issue is:open repo:microsoft/${repo} in:title "${title}" author:playwrightmachine` q: `is:issue is:open repo:microsoft/${repo} in:title "${title}"`
}) })
let issueNumber = null; let issueNumber = null;
let issueBody = ''; let issueBody = '';
@ -54,7 +48,7 @@ jobs:
issueBody = issueCreateData.body; issueBody = issueCreateData.body;
} }
const newBody = issueBody.trimEnd() + ` 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({ const data = await github.rest.issues.update({
owner: context.repo.owner, owner: context.repo.owner,
repo: repo, repo: repo,

View file

@ -14,7 +14,7 @@ env:
jobs: jobs:
publish-canary: publish-canary:
name: "publish canary NPM" name: "publish canary NPM"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright' if: github.repository == 'microsoft/playwright'
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed
@ -65,7 +65,7 @@ jobs:
publish-trace-viewer: publish-trace-viewer:
name: "publish Trace Viewer to trace.playwright.dev" name: "publish Trace Viewer to trace.playwright.dev"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright' if: github.repository == 'microsoft/playwright'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -2,6 +2,12 @@ name: "publish release - Docker"
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
is_release:
required: true
type: boolean
description: "Is this a release image?"
release: release:
types: [published] types: [published]
@ -39,3 +45,6 @@ jobs:
- name: Login to ACR via OIDC - name: Login to ACR via OIDC
run: az acr login --name playwright run: az acr login --name playwright
- run: ./utils/docker/publish_docker.sh stable - run: ./utils/docker/publish_docker.sh stable
if: (github.event_name != 'workflow_dispatch' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release == 'true')
- run: ./utils/docker/publish_docker.sh canary
if: (github.event_name != 'workflow_dispatch' && github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release != 'true')

View file

@ -10,7 +10,7 @@ env:
jobs: jobs:
publish-driver-release: publish-driver-release:
name: "publish playwright driver to CDN" name: "publish playwright driver to CDN"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright' if: github.repository == 'microsoft/playwright'
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed

View file

@ -10,7 +10,7 @@ env:
jobs: jobs:
publish-npm-release: publish-npm-release:
name: "publish to NPM" name: "publish to NPM"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright' if: github.repository == 'microsoft/playwright'
permissions: permissions:
contents: read contents: read

View file

@ -7,7 +7,7 @@ on:
jobs: jobs:
publish-trace-viewer: publish-trace-viewer:
name: "publish Trace Viewer to trace.playwright.dev" name: "publish Trace Viewer to trace.playwright.dev"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright' if: github.repository == 'microsoft/playwright'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View file

@ -3,28 +3,16 @@ name: Roll Browser into Playwright
on: on:
repository_dispatch: repository_dispatch:
types: [roll_into_pw] 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: env:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1 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: permissions:
contents: write contents: write
jobs: jobs:
roll: roll:
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
@ -36,21 +24,21 @@ jobs:
run: npx playwright install-deps run: npx playwright install-deps
- name: Roll to new revision - name: Roll to new revision
run: | run: |
./utils/roll_browser.js $BROWSER $REVISION ./utils/roll_browser.js ${{ github.event.client_payload.browser }} ${{ github.event.client_payload.revision }}
npm run build npm run build
- name: Prepare branch - name: Prepare branch
id: prepare-branch id: prepare-branch
run: | 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 echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
git config --global user.name github-actions git config --global user.name github-actions
git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com
git checkout -b "$BRANCH_NAME" git checkout -b "$BRANCH_NAME"
git add . git add .
git commit -m "feat(${BROWSER}): roll to r${REVISION}" git commit -m "feat(${{ github.event.client_payload.browser }}): roll to r${{ github.event.client_payload.revision }}"
git push origin $BRANCH_NAME --force git push origin $BRANCH_NAME
- name: Create Pull Request - name: Create Pull Request
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
script: | script: |
@ -59,7 +47,7 @@ jobs:
repo: 'playwright', repo: 'playwright',
head: 'microsoft:${{ steps.prepare-branch.outputs.BRANCH_NAME }}', head: 'microsoft:${{ steps.prepare-branch.outputs.BRANCH_NAME }}',
base: 'main', 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({ await github.rest.issues.addLabels({
owner: 'microsoft', owner: 'microsoft',

View file

@ -35,7 +35,7 @@ jobs:
git push origin $BRANCH_NAME git push origin $BRANCH_NAME
- name: Create Pull Request - name: Create Pull Request
if: ${{ steps.prepare-branch.outputs.HAS_CHANGES == '1' }} if: ${{ steps.prepare-branch.outputs.HAS_CHANGES == '1' }}
uses: actions/github-script@v7 uses: actions/github-script@v6
with: with:
github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }} github-token: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
script: | script: |

View file

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

View file

@ -27,9 +27,9 @@ jobs:
node-version: [18] node-version: [18]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
node-version: 20 node-version: 16
- os: ubuntu-latest - os: ubuntu-latest
node-version: 22 node-version: 20
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

56
.github/workflows/tests_electron.yml vendored Normal file
View file

@ -0,0 +1,56 @@
name: "electron"
on:
push:
branches:
- main
- release-*
pull_request:
paths-ignore:
- 'browser_patches/**'
- 'docs/**'
types: [ labeled ]
branches:
- main
- release-*
env:
# Force terminal colors. @see https://www.npmjs.com/package/colors
FORCE_COLOR: 1
jobs:
test_electron:
name: ${{ matrix.os }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
- run: npm run build
- run: npx playwright install --with-deps chromium
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run etest
if: matrix.os == 'ubuntu-latest'
- run: npm run etest
if: matrix.os != 'ubuntu-latest'
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: ${{ !cancelled() }}
shell: bash

View file

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

View file

@ -36,26 +36,45 @@ jobs:
node-version: [18] node-version: [18]
include: include:
- os: ubuntu-22.04 - os: ubuntu-22.04
node-version: 20 node-version: 16
browser: chromium browser: chromium
- os: ubuntu-22.04 - os: ubuntu-22.04
node-version: 22 node-version: 20
browser: chromium browser: chromium
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
PWTEST_BOT_NAME: "${{ matrix.browser }}-${{ matrix.os }}-node${{ matrix.node-version }}"
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed contents: read # This is required for actions/checkout to succeed
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/run-test - uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
browsers-to-install: ${{ matrix.browser }} chromium - run: npm ci
command: npm run test -- --project=${{ matrix.browser }}-* env:
bot-name: "${{ matrix.browser }}-${{ matrix.os }}-node${{ matrix.node-version }}" DEBUG: pw:install
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} - run: npm run build
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - run: npx playwright install --with-deps ${{ matrix.browser }} chromium
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-*
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: ${{ !cancelled() }}
shell: bash
- name: Upload blob report
if: ${{ !cancelled() }}
uses: ./.github/actions/upload-blob-report
with:
report_dir: blob-report
job_name: ${{ env.PWTEST_BOT_NAME }}
test_linux_chromium_tot: test_linux_chromium_tot:
name: ${{ matrix.os }} (chromium tip-of-tree) name: ${{ matrix.os }} (chromium tip-of-tree)
@ -65,21 +84,42 @@ jobs:
matrix: matrix:
os: [ubuntu-20.04] os: [ubuntu-20.04]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
PWTEST_BOT_NAME: "${{ matrix.os }}-chromium-tip-of-tree"
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed contents: read # This is required for actions/checkout to succeed
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/run-test - uses: actions/setup-node@v4
with: with:
browsers-to-install: chromium-tip-of-tree node-version: 18
command: npm run test -- --project=chromium-* - run: npm ci
bot-name: "${{ matrix.os }}-chromium-tip-of-tree" env:
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} DEBUG: pw:install
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - run: npm run build
- run: npx playwright install --with-deps chromium-tip-of-tree
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=chromium-*
env: env:
PWTEST_CHANNEL: chromium-tip-of-tree PWTEST_CHANNEL: chromium-tip-of-tree
PWTEST_BOT_NAME: "${{ matrix.os }}-chromium-tip-of-tree"
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: ${{ !cancelled() }}
shell: bash
- name: Upload blob report
if: ${{ !cancelled() }}
uses: ./.github/actions/upload-blob-report
with:
report_dir: blob-report
job_name: ${{ env.PWTEST_BOT_NAME }}
test_test_runner: test_test_runner:
name: Test Runner name: Test Runner
@ -93,37 +133,57 @@ jobs:
shardTotal: [2] shardTotal: [2]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
node-version: 20 node-version: 16
shardIndex: 1 shardIndex: 1
shardTotal: 2 shardTotal: 2
- os: ubuntu-latest - os: ubuntu-latest
node-version: 20 node-version: 16
shardIndex: 2 shardIndex: 2
shardTotal: 2 shardTotal: 2
- os: ubuntu-latest - os: ubuntu-latest
node-version: 22 node-version: 20
shardIndex: 1 shardIndex: 1
shardTotal: 2 shardTotal: 2
- os: ubuntu-latest - os: ubuntu-latest
node-version: 22 node-version: 20
shardIndex: 2 shardIndex: 2
shardTotal: 2 shardTotal: 2
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
PWTEST_BOT_NAME: "${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.shardIndex }}"
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed contents: read # This is required for actions/checkout to succeed
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/run-test - uses: actions/setup-node@v4
with: with:
node-version: ${{matrix.node-version}} node-version: ${{matrix.node-version}}
command: npm run ttest -- --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - run: npm ci
bot-name: "${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.shardIndex }}"
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: env:
PWTEST_CHANNEL: firefox-beta DEBUG: pw:install
- run: npm run build
- run: npx playwright install --with-deps
- run: npm run ttest -- --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
if: matrix.os != 'ubuntu-latest'
- run: xvfb-run npm run ttest -- --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
if: matrix.os == 'ubuntu-latest'
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: ${{ !cancelled() }}
shell: bash
- name: Upload blob report
if: ${{ !cancelled() }}
uses: ./.github/actions/upload-blob-report
with:
report_dir: blob-report
job_name: ${{ env.PWTEST_BOT_NAME }}
test_web_components: test_web_components:
name: Web Components name: Web Components
@ -208,25 +268,40 @@ jobs:
- windows-latest - windows-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 30 timeout-minutes: 30
env:
PWTEST_BOT_NAME: "package-installations-${{ matrix.os }}"
permissions: permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed contents: read # This is required for actions/checkout to succeed
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
env:
DEBUG: pw:install
- run: npm run build
- run: npx playwright install --with-deps
- run: npm install -g yarn@1 - run: npm install -g yarn@1
- run: npm install -g pnpm@8 - run: npm install -g pnpm@8
- name: Setup Ubuntu Binary Installation # TODO: Remove when https://github.com/electron/electron/issues/42510 is fixed - run: npm run itest
if: ${{ runner.os == 'Linux' }} if: matrix.os != 'ubuntu-latest'
run: | - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run itest
if grep -q "Ubuntu 24" /etc/os-release; then if: matrix.os == 'ubuntu-latest'
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Azure Login
fi uses: azure/login@v2
shell: bash if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
- uses: ./.github/actions/run-test
with: with:
command: npm run itest client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
bot-name: "package-installations-${{ matrix.os }}" tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
shell: ${{ matrix.os == 'windows-latest' && 'pwsh' || 'bash' }} subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} if: ${{ !cancelled() }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} shell: bash
- name: Upload blob report
if: ${{ !cancelled() }}
uses: ./.github/actions/upload-blob-report
with:
report_dir: blob-report
job_name: ${{ env.PWTEST_BOT_NAME }}

File diff suppressed because it is too large Load diff

View file

@ -34,7 +34,7 @@ jobs:
PLAYWRIGHT_SERVICE_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }} PLAYWRIGHT_SERVICE_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }}
- name: Upload blob report to GitHub - name: Upload blob report to GitHub
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: all-blob-reports name: all-blob-reports
path: blob-report path: blob-report
@ -53,7 +53,7 @@ jobs:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
- run: npm run build - run: npm run build
- name: Download blob report artifact - name: Download blob report artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
name: all-blob-reports name: all-blob-reports
path: all-blob-reports path: all-blob-reports

58
.github/workflows/tests_stress.yml vendored Normal file
View file

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

View file

@ -26,13 +26,25 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ./.github/actions/run-test - uses: actions/setup-node@v4
with: with:
browsers-to-install: ${{ matrix.browser }} chromium node-version: 18
command: npm run test -- --project=${{ matrix.browser }}-* - run: npm ci
bot-name: "${{ matrix.browser }}-${{ matrix.os }}" env:
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} DEBUG: pw:install
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - run: npm run build
- run: npx playwright install --with-deps ${{ matrix.browser }} chromium
- run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-*
env: env:
PWTEST_VIDEO: 1 PWTEST_VIDEO: 1
- name: Azure Login
uses: azure/login@v2
if: ${{ !cancelled() && github.event_name == 'push' && github.repository == 'microsoft/playwright' }}
with:
client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: ${{ !cancelled() }}
shell: bash

60
.github/workflows/tests_webview2.yml vendored Normal file
View file

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

View file

@ -9,7 +9,7 @@ on:
jobs: jobs:
trigger: trigger:
name: "trigger" name: "trigger"
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
steps: steps:
- run: | - run: |
curl -X POST \ curl -X POST \

2
.gitignore vendored
View file

@ -34,5 +34,3 @@ test-results
/tests/installation/.registry.json /tests/installation/.registry.json
.cache/ .cache/
.eslintcache .eslintcache
playwright.env
/firefox/

View file

@ -1,87 +1,88 @@
# Contributing # 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 ```bash
node --version 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 ```bash
git clone https://github.com/microsoft/playwright git clone https://github.com/microsoft/playwright
cd playwright cd playwright
``` ```
Install dependencies and run the build in watch mode. 2. Install dependencies
```bash ```bash
npm ci npm ci
npm run watch
npx playwright install
``` ```
**Experimental dev mode with Hot Module Replacement for recorder/trace-viewer/UI Mode** 3. Build Playwright
``` ```bash
PW_HMR=1 npm run watch npm run build
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). 4. Run all Playwright tests locally. For more information about tests, read [Running & Writing Tests](#running--writing-tests).
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). ```bash
npm test
```
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: ### Code reviews
```bash
npm run lint
```
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. 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.
### Write documentation ### Code Style
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. - 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.
Various other files are generated from the API specification. If you are running `npm run watch`, these will be re-generated automatically. To run code linter, use:
Larger changes will require updates to the documentation guides as well. This will be made clear during the code review. ```bash
npm run eslint
```
## Add a test ### API guidelines
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. When authoring new API methods, consider the following:
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: - Expose as little information as needed. When in doubt, dont 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.
- Library tests cover APIs not related to the test runner. ### Commit Messages
```bash
# fast path runs all tests in Chromium
npm run ctest
# slow path runs all tests in three browsers Commit messages should follow the Semantic Commit Messages format:
npm run test
```
- Test runner tests.
```bash
npm run ttest
```
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).
Note that tests should be *hermetic*, and not depend on external services. Tests should work on all three platforms: macOS, Linux and Windows.
## Write a commit message
Commit messages should follow the [Semantic Commit Messages](https://www.conventionalcommits.org/en/v1.0.0/) format:
``` ```
label(namespace): title label(namespace): title
@ -92,57 +93,144 @@ footer
``` ```
1. *label* is one of the following: 1. *label* is one of the following:
- `fix` - bug fixes - `fix` - playwright bug fixes.
- `feat` - new features - `feat` - playwright features.
- `docs` - documentation-only changes - `docs` - changes to docs, e.g. `docs(api.md): ..` to change documentation.
- `test` - test-only changes - `test` - changes to playwright tests infrastructure.
- `devops` - changes to the CI or build - `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 - `chore` - everything that doesn't fall under previous categories
1. *namespace* is put in parenthesis after label and is optional. Must be lowercase. 2. *namespace* is put in parenthesis after label and is optional. Must be lowercase.
1. *title* is a brief summary of changes. 3. *title* is a brief summary of changes.
1. *description* is **optional**, new-line separated from title and is in present tense. 4. *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. 5. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues.
Example: Example:
``` ```
feat(trace viewer): network panel filtering fix(firefox): make sure session cookies work
This patch adds a filtering toolbar to the network panel. This patch fixes session cookies in the firefox browser.
<link to a screenshot>
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 all tests in Chromium
```bash
npm run ctest # also `ftest` for firefox and `wtest` for WebKit
```
- 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 ```bash
CRPATH=<path-to-executable> npm run ctest CRPATH=<path-to-executable> npm run ctest
``` ```
You will also find `DEBUG=pw:browser` useful for debugging custom builds. - To run tests in slow-mode:
**Building documentation site** ```bash
SLOW_MO=500 npm run wtest -- --headed
```
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. - When should a test be marked with `skip` or `fail`?
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: - **`skip(condition)`**: This test *should ***never*** work* for `condition`
1. Clone the [microsoft/playwright.dev](https://github.com/microsoft/playwright.dev) repo. where `condition` is usually a certain browser like `FFOX` (for Firefox),
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. `WEBKIT` (for WebKit), and `CHROMIUM` (for Chromium).
1. Follow [the playwright.dev README instructions to "run dev server"](https://github.com/microsoft/playwright.dev/#run-dev-server) to view your changes.
For example, the [alt-click downloads test](https://github.com/microsoft/playwright/blob/471ccc72d3f0847caa36f629b394a028c7750d93/test/download.spec.js#L86) is marked
with `skip(FFOX)` since an alt-click in Firefox will not produce a download
even if a person was driving the browser.
- **`fail(condition)`**: This test *should ***eventually*** work* for `condition`
where `condition` is usually a certain browser like `FFOX` (for Firefox),
`WEBKIT` (for WebKit), and `CHROMIUM` (for Chromium).
For example, the [alt-click downloads test](https://github.com/microsoft/playwright/blob/471ccc72d3f0847caa36f629b394a028c7750d93/test/download.spec.js#L86) is marked
with `fail(CHROMIUM || WEBKIT)` since Playwright performing these actions
currently diverges from what a user would experience driving a Chromium or
WebKit.
## Contributor License Agreement ## Contributor License Agreement

View file

@ -1,35 +0,0 @@
# How to File a Bug Report That Actually Gets Resolved
Make sure youre 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 cant reproduce them ourselves.
- We cant 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 Overflows 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 its unlikely we can assist.

View file

@ -1,6 +1,6 @@
# 🎭 Playwright # 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-134.0.6998.35-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-135.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) [![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-125.0.6422.26-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-125.0.1-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) ## [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 | | | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: | | :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->134.0.6998.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Chromium <!-- GEN:chromium-version -->125.0.6422.26<!-- 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: | | WebKit <!-- GEN:webkit-version -->17.4<!-- 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: | | Firefox <!-- GEN:firefox-version -->125.0.1<!-- 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. 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). 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) * [Getting started](https://playwright.dev/docs/intro)
* [Installation configuration](https://playwright.dev/docs/installation)
* [API reference](https://playwright.dev/docs/api/class-playwright) * [API reference](https://playwright.dev/docs/api/class-playwright)
## Capabilities ## Capabilities
@ -162,7 +163,7 @@ test('Intercept network requests', async ({ page }) => {
## Resources ## Resources
* [Documentation](https://playwright.dev) * [Documentation](https://playwright.dev/docs/intro)
* [API reference](https://playwright.dev/docs/api/class-playwright/) * [API reference](https://playwright.dev/docs/api/class-playwright/)
* [Contribution guide](CONTRIBUTING.md) * [Contribution guide](CONTRIBUTING.md)
* [Changelog](https://github.com/microsoft/playwright/releases) * [Changelog](https://github.com/microsoft/playwright/releases)

View file

@ -1,18 +1,18 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK --> <!-- BEGIN MICROSOFT SECURITY.MD V0.0.3 BLOCK -->
## Security ## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
## Reporting Security Issues ## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.** **Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
@ -28,7 +28,7 @@ Please include the requested information listed below (as much as you can provid
This information will help us triage your report more quickly. This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages ## Preferred Languages
@ -36,6 +36,6 @@ We prefer all communications to be in English.
## Policy ## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK --> <!-- END MICROSOFT SECURITY.MD BLOCK -->

View file

@ -1,17 +0,0 @@
# Support
## How to file issues and get help
This project uses GitHub issues to track bugs and feature requests. Please search the [existing issues][gh-issues] before filing new ones to avoid duplicates. For new issues, file your bug or feature request as a new issue using corresponding template.
For help and questions about using this project, please see the [docs site for Playwright][docs].
Join our community [Discord Server][discord-server] to connect with other developers using Playwright and ask questions in our 'help-playwright' forum.
## Microsoft Support Policy
Support for Playwright is limited to the resources listed above.
[gh-issues]: https://github.com/microsoft/playwright/issues/
[docs]: https://playwright.dev/
[discord-server]: https://aka.ms/playwright/discord

View file

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

View file

@ -145,13 +145,9 @@ class NetworkRequest {
} }
this._expectingInterception = false; this._expectingInterception = false;
this._expectingResumedRequest = undefined; // { method, headers, postData } this._expectingResumedRequest = undefined; // { method, headers, postData }
this._overriddenHeadersForRedirect = redirectedFrom?._overriddenHeadersForRedirect;
this._sentOnResponse = false; this._sentOnResponse = false;
this._fulfilled = false;
if (this._overriddenHeadersForRedirect) if (this._pageNetwork)
overrideRequestHeaders(httpChannel, this._overriddenHeadersForRedirect);
else if (this._pageNetwork)
appendExtraHTTPHeaders(httpChannel, this._pageNetwork.combinedExtraHTTPHeaders()); appendExtraHTTPHeaders(httpChannel, this._pageNetwork.combinedExtraHTTPHeaders());
this._responseBodyChunks = []; this._responseBodyChunks = [];
@ -198,7 +194,6 @@ class NetworkRequest {
// Public interception API. // Public interception API.
fulfill(status, statusText, headers, base64body) { fulfill(status, statusText, headers, base64body) {
this._fulfilled = true;
this._interceptedChannel.synthesizeStatus(status, statusText); this._interceptedChannel.synthesizeStatus(status, statusText);
for (const header of headers) { for (const header of headers) {
this._interceptedChannel.synthesizeHeader(header.name, header.value); this._interceptedChannel.synthesizeHeader(header.name, header.value);
@ -233,13 +228,20 @@ class NetworkRequest {
if (!this._expectingResumedRequest) if (!this._expectingResumedRequest)
return; return;
const { method, headers, postData } = this._expectingResumedRequest; const { method, headers, postData } = this._expectingResumedRequest;
this._overriddenHeadersForRedirect = headers;
this._expectingResumedRequest = undefined; this._expectingResumedRequest = undefined;
if (headers) if (headers) {
overrideRequestHeaders(this.httpChannel, headers); for (const header of requestHeaders(this.httpChannel)) {
else if (this._pageNetwork) // 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()); appendExtraHTTPHeaders(this.httpChannel, this._pageNetwork.combinedExtraHTTPHeaders());
}
if (method) if (method)
this.httpChannel.requestMethod = method; this.httpChannel.requestMethod = method;
if (postData !== undefined) if (postData !== undefined)
@ -598,8 +600,6 @@ class NetworkObserver {
proxyFilter.onProxyFilterResult(defaultProxyInfo); proxyFilter.onProxyFilterResult(defaultProxyInfo);
return; return;
} }
if (this._targetRegistry.shouldBustHTTPAuthCacheForProxy(proxy))
Services.obs.notifyObservers(null, "net:clear-active-logins");
proxyFilter.onProxyFilterResult(protocolProxyService.newProxyInfo( proxyFilter.onProxyFilterResult(protocolProxyService.newProxyInfo(
proxy.type, proxy.type,
proxy.host, proxy.host,
@ -769,20 +769,6 @@ function requestHeaders(httpChannel) {
return headers; 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) { function causeTypeToString(causeType) {
for (let key in Ci.nsIContentPolicy) { for (let key in Ci.nsIContentPolicy) {
if (Ci.nsIContentPolicy[key] === causeType) if (Ci.nsIContentPolicy[key] === causeType)
@ -815,8 +801,7 @@ class ResponseStorage {
return; return;
} }
let encodings = []; let encodings = [];
// Note: fulfilled request comes with decoded body right away. if ((request.httpChannel instanceof Ci.nsIEncodedChannel) && request.httpChannel.contentEncodings && !request.httpChannel.applyConversion) {
if ((request.httpChannel instanceof Ci.nsIEncodedChannel) && request.httpChannel.contentEncodings && !request.httpChannel.applyConversion && !request._fulfilled) {
const encodingHeader = request.httpChannel.getResponseHeader("Content-Encoding"); const encodingHeader = request.httpChannel.getResponseHeader("Content-Encoding");
encodings = encodingHeader.split(/\s*\t*,\s*\t*/); encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
} }

View file

@ -116,7 +116,6 @@ class TargetRegistry {
this._browserToTarget = new Map(); this._browserToTarget = new Map();
this._browserIdToTarget = new Map(); this._browserIdToTarget = new Map();
this._proxiesWithClashingAuthCacheKeys = new Set();
this._browserProxy = null; this._browserProxy = null;
// Cleanup containers from previous runs (if any) // Cleanup containers from previous runs (if any)
@ -235,50 +234,12 @@ class TargetRegistry {
onOpenWindow(win); 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) { async cancelDownload(options) {
this._downloadInterceptor.cancelDownload(options.uuid); this._downloadInterceptor.cancelDownload(options.uuid);
} }
setBrowserProxy(proxy) { setBrowserProxy(proxy) {
this._browserProxy = proxy; this._browserProxy = proxy;
this._updateProxiesWithSameAuthCacheAndDifferentCredentials();
} }
getProxyInfo(channel) { getProxyInfo(channel) {
@ -393,8 +354,7 @@ class PageTarget {
this._videoRecordingInfo = undefined; this._videoRecordingInfo = undefined;
this._screencastRecordingInfo = undefined; this._screencastRecordingInfo = undefined;
this._dialogs = new Map(); this._dialogs = new Map();
this.forcedColors = 'none'; this.forcedColors = 'no-override';
this.disableCache = false;
this.mediumOverride = ''; this.mediumOverride = '';
this.crossProcessCookie = { this.crossProcessCookie = {
initScripts: [], initScripts: [],
@ -407,7 +367,7 @@ class PageTarget {
onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation), onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
}; };
this._eventListeners = [ this._eventListeners = [
helper.addObserver(this._updateModalDialogs.bind(this), 'common-dialog-loaded'), helper.addObserver(this._updateModalDialogs.bind(this), 'tabmodal-dialog-loaded'),
helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION), helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),
helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()), helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
helper.addEventListener(this._linkedBrowser, 'WillChangeBrowserRemoteness', event => this._willChangeBrowserRemoteness()), helper.addEventListener(this._linkedBrowser, 'WillChangeBrowserRemoteness', event => this._willChangeBrowserRemoteness()),
@ -501,26 +461,12 @@ class PageTarget {
this.updateReducedMotionOverride(browsingContext); this.updateReducedMotionOverride(browsingContext);
this.updateForcedColorsOverride(browsingContext); this.updateForcedColorsOverride(browsingContext);
this.updateForceOffline(browsingContext); this.updateForceOffline(browsingContext);
this.updateCacheDisabled(browsingContext);
} }
updateForceOffline(browsingContext = undefined) { updateForceOffline(browsingContext = undefined) {
(browsingContext || this._linkedBrowser.browsingContext).forceOffline = this._browserContext.forceOffline; (browsingContext || this._linkedBrowser.browsingContext).forceOffline = this._browserContext.forceOffline;
} }
setCacheDisabled(disabled) {
this.disableCache = disabled;
this.updateCacheDisabled();
}
updateCacheDisabled(browsingContext = this._linkedBrowser.browsingContext) {
const enableFlags = Ci.nsIRequest.LOAD_NORMAL;
const disableFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
browsingContext.defaultLoadFlags = (this._browserContext.disableCache || this.disableCache) ? disableFlags : enableFlags;
}
updateTouchOverride(browsingContext = undefined) { updateTouchOverride(browsingContext = undefined) {
(browsingContext || this._linkedBrowser.browsingContext).touchEventsOverride = this._browserContext.touchOverride ? 'enabled' : 'none'; (browsingContext || this._linkedBrowser.browsingContext).touchEventsOverride = this._browserContext.touchOverride ? 'enabled' : 'none';
} }
@ -538,7 +484,7 @@ class PageTarget {
} }
_updateModalDialogs() { _updateModalDialogs() {
const prompts = new Set(this._linkedBrowser.tabDialogBox.getContentDialogManager().dialogs.map(dialog => dialog.frameContentWindow.Dialog)); const prompts = new Set(this._linkedBrowser.tabModalPromptBox ? this._linkedBrowser.tabModalPromptBox.listPrompts() : []);
for (const dialog of this._dialogs.values()) { for (const dialog of this._dialogs.values()) {
if (!prompts.has(dialog.prompt())) { if (!prompts.has(dialog.prompt())) {
this._dialogs.delete(dialog.id()); this._dialogs.delete(dialog.id());
@ -635,8 +581,7 @@ class PageTarget {
} }
updateForcedColorsOverride(browsingContext = undefined) { updateForcedColorsOverride(browsingContext = undefined) {
const isActive = this.forcedColors === 'active' || this._browserContext.forcedColors === 'active'; (browsingContext || this._linkedBrowser.browsingContext).forcedColorsOverride = (this.forcedColors !== 'no-override' ? this.forcedColors : this._browserContext.forcedColors) || 'no-override';
(browsingContext || this._linkedBrowser.browsingContext).forcedColorsOverride = isActive ? 'active' : 'none';
} }
async setInterceptFileChooserDialog(enabled) { async setInterceptFileChooserDialog(enabled) {
@ -859,8 +804,8 @@ function fromProtocolReducedMotion(reducedMotion) {
function fromProtocolForcedColors(forcedColors) { function fromProtocolForcedColors(forcedColors) {
if (forcedColors === 'active' || forcedColors === 'none') if (forcedColors === 'active' || forcedColors === 'none')
return forcedColors; return forcedColors;
if (!forcedColors) if (forcedColors === null)
return 'none'; return undefined;
throw new Error('Unknown forced colors: ' + forcedColors); throw new Error('Unknown forced colors: ' + forcedColors);
} }
@ -892,9 +837,8 @@ class BrowserContext {
this.defaultPlatform = null; this.defaultPlatform = null;
this.touchOverride = false; this.touchOverride = false;
this.forceOffline = false; this.forceOffline = false;
this.disableCache = false;
this.colorScheme = 'none'; this.colorScheme = 'none';
this.forcedColors = 'none'; this.forcedColors = 'no-override';
this.reducedMotion = 'none'; this.reducedMotion = 'none';
this.videoRecordingOptions = undefined; this.videoRecordingOptions = undefined;
this.crossProcessCookie = { this.crossProcessCookie = {
@ -946,14 +890,12 @@ class BrowserContext {
} }
this._registry._browserContextIdToBrowserContext.delete(this.browserContextId); this._registry._browserContextIdToBrowserContext.delete(this.browserContextId);
this._registry._userContextIdToBrowserContext.delete(this.userContextId); this._registry._userContextIdToBrowserContext.delete(this.userContextId);
this._registry._updateProxiesWithSameAuthCacheAndDifferentCredentials();
} }
setProxy(proxy) { setProxy(proxy) {
// Clear AuthCache. // Clear AuthCache.
Services.obs.notifyObservers(null, "net:clear-active-logins"); Services.obs.notifyObservers(null, "net:clear-active-logins");
this._proxy = proxy; this._proxy = proxy;
this._registry._updateProxiesWithSameAuthCacheAndDifferentCredentials();
} }
setIgnoreHTTPSErrors(ignoreHTTPSErrors) { setIgnoreHTTPSErrors(ignoreHTTPSErrors) {
@ -996,12 +938,6 @@ class BrowserContext {
page.updateForceOffline(); page.updateForceOffline();
} }
setCacheDisabled(disabled) {
this.disableCache = disabled;
for (const page of this.pages)
page.updateCacheDisabled();
}
async setDefaultViewport(viewport) { async setDefaultViewport(viewport) {
this.defaultViewportSize = viewport ? viewport.viewportSize : undefined; this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined; this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined;

View file

@ -105,10 +105,7 @@ class Juggler {
}; };
// Force create hidden window here, otherwise its creation later closes the web socket! // 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; Services.appShell.hiddenDOMWindow;
}
let pipeStopped = false; let pipeStopped = false;
let browserHandler; let browserHandler;

View file

@ -46,6 +46,8 @@ class FrameTree {
Ci.nsISupportsWeakReference, Ci.nsISupportsWeakReference,
]); ]);
this._addedScrollbarsStylesheetSymbol = Symbol('_addedScrollbarsStylesheetSymbol');
this._wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].createInstance(Ci.nsIWorkerDebuggerManager); this._wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].createInstance(Ci.nsIWorkerDebuggerManager);
this._wdmListener = { this._wdmListener = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerManagerListener]), QueryInterface: ChromeUtils.generateQI([Ci.nsIWorkerDebuggerManagerListener]),
@ -128,12 +130,24 @@ class FrameTree {
} }
_onDOMWindowCreated(window) { _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); const frame = this.frameForDocShell(window.docShell);
if (!frame) if (!frame)
return; return;
frame._onGlobalObjectCleared(); frame._onGlobalObjectCleared();
} }
setScrollbarsHidden(hidden) {
this.scrollbarsHidden = hidden;
}
setJavaScriptDisabled(javaScriptDisabled) { setJavaScriptDisabled(javaScriptDisabled) {
this._javaScriptDisabled = javaScriptDisabled; this._javaScriptDisabled = javaScriptDisabled;
for (const frame of this.frames()) for (const frame of this.frames())

View file

@ -8,8 +8,6 @@ const helper = new Helper();
let sameProcessInstanceNumber = 0; let sameProcessInstanceNumber = 0;
const topBrowingContextToAgents = new Map();
class JugglerFrameChild extends JSWindowActorChild { class JugglerFrameChild extends JSWindowActorChild {
constructor() { constructor() {
super(); super();
@ -18,66 +16,46 @@ class JugglerFrameChild extends JSWindowActorChild {
} }
handleEvent(aEvent) { handleEvent(aEvent) {
const agents = this._agents(); if (this._agents && aEvent.type === 'DOMWillOpenModalDialog') {
if (!agents) this._agents.channel.pause();
return;
if (aEvent.type === 'DOMWillOpenModalDialog') {
agents.channel.pause();
return; return;
} }
if (aEvent.type === 'DOMModalDialogClosed') { if (this._agents && aEvent.type === 'DOMModalDialogClosed') {
agents.channel.resumeSoon(); this._agents.channel.resumeSoon();
return; return;
} }
if (aEvent.target === this.document) { if (this._agents && aEvent.target === this.document)
agents.pageAgent.onWindowEvent(aEvent); this._agents.pageAgent.onWindowEvent(aEvent);
agents.frameTree.onWindowEvent(aEvent); if (this._agents && aEvent.target === this.document)
} this._agents.frameTree.onWindowEvent(aEvent);
}
_agents() {
return topBrowingContextToAgents.get(this.browsingContext.top);
} }
actorCreated() { actorCreated() {
this.actorName = `content::${this.browsingContext.browserId}/${this.browsingContext.id}/${++sameProcessInstanceNumber}`; this.actorName = `content::${this.browsingContext.browserId}/${this.browsingContext.id}/${++sameProcessInstanceNumber}`;
this._eventListeners.push(helper.addEventListener(this.contentWindow, 'load', event => { this._eventListeners.push(helper.addEventListener(this.contentWindow, 'load', event => {
this._agents()?.pageAgent.onWindowEvent(event); this._agents?.pageAgent.onWindowEvent(event);
})); }));
if (this.document.documentURI.startsWith('moz-extension://')) if (this.document.documentURI.startsWith('moz-extension://'))
return; return;
this._agents = initialize(this.browsingContext, this.docShell, this);
// Child frame events will be forwarded to related top-level agents.
if (this.browsingContext.parent)
return;
let agents = topBrowingContextToAgents.get(this.browsingContext);
if (!agents) {
agents = initialize(this.browsingContext, this.docShell);
topBrowingContextToAgents.set(this.browsingContext, agents);
} }
agents.channel.bindToActor(this);
agents.actor = this; _dispose() {
helper.removeListeners(this._eventListeners);
// We do not cleanup since agents are shared for all frames in the process.
// TODO: restore the cleanup.
// Reset transport so that all messages will be pending and will not throw any errors.
// this._channel.resetTransport();
// this._agents.pageAgent.dispose();
// this._agents.frameTree.dispose();
// this._agents = undefined;
} }
didDestroy() { didDestroy() {
helper.removeListeners(this._eventListeners); this._dispose();
if (this.browsingContext.parent)
return;
const agents = topBrowingContextToAgents.get(this.browsingContext);
// The agents are already re-bound to a new actor.
if (agents?.actor !== this)
return;
topBrowingContextToAgents.delete(this.browsingContext);
agents.channel.resetTransport();
agents.pageAgent.dispose();
agents.frameTree.dispose();
} }
receiveMessage() { } receiveMessage() { }

View file

@ -120,8 +120,7 @@ class PageAgent {
// After the dragStart event is dispatched and handled by Web, // 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. // it might or might not create a new drag session, depending on its preventing default.
setTimeout(() => { setTimeout(() => {
const session = this._getCurrentDragSession(); this._browserPage.emit('pageInputEvent', { type: 'juggler-drag-finalized', dragSessionStarted: !!dragService.getCurrentSession() });
this._browserPage.emit('pageInputEvent', { type: 'juggler-drag-finalized', dragSessionStarted: !!session });
}, 0); }, 0);
} }
}), }),
@ -153,6 +152,7 @@ class PageAgent {
getFullAXTree: this._getFullAXTree.bind(this), getFullAXTree: this._getFullAXTree.bind(this),
insertText: this._insertText.bind(this), insertText: this._insertText.bind(this),
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this), scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
setCacheDisabled: this._setCacheDisabled.bind(this),
setFileInputFiles: this._setFileInputFiles.bind(this), setFileInputFiles: this._setFileInputFiles.bind(this),
evaluate: this._runtime.evaluate.bind(this._runtime), evaluate: this._runtime.evaluate.bind(this._runtime),
callFunction: this._runtime.callFunction.bind(this._runtime), callFunction: this._runtime.callFunction.bind(this._runtime),
@ -162,6 +162,15 @@ class PageAgent {
]; ];
} }
_setCacheDisabled({cacheDisabled}) {
const enable = Ci.nsIRequest.LOAD_NORMAL;
const disable = Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
const docShell = this._frameTree.mainFrame().docShell();
docShell.defaultLoadFlags = cacheDisabled ? disable : enable;
}
_emitAllEvents(frame) { _emitAllEvents(frame) {
this._browserPage.emit('pageEventFired', { this._browserPage.emit('pageEventFired', {
frameId: frame.id(), frameId: frame.id(),
@ -371,12 +380,7 @@ class PageAgent {
const unsafeObject = frame.unsafeObject(objectId); const unsafeObject = frame.unsafeObject(objectId);
if (!unsafeObject) if (!unsafeObject)
throw new Error('Object is not input!'); throw new Error('Object is not input!');
let nsFiles; const nsFiles = await Promise.all(files.map(filePath => File.createFromFileName(filePath)));
if (unsafeObject.webkitdirectory) {
nsFiles = await new Directory(files[0]).getFiles(true);
} else {
nsFiles = await Promise.all(files.map(filePath => File.createFromFileName(filePath)));
}
unsafeObject.mozSetFileArray(nsFiles); unsafeObject.mozSetFileArray(nsFiles);
const events = [ const events = [
new (frame.domWindow().Event)('input', { bubbles: true, cancelable: true, composed: true }), new (frame.domWindow().Event)('input', { bubbles: true, cancelable: true, composed: true }),
@ -515,26 +519,75 @@ class PageAgent {
false /* aIgnoreRootScrollFrame */, false /* aIgnoreRootScrollFrame */,
true /* aFlushLayout */); true /* aFlushLayout */);
await this._dispatchTouchEvent({ const {defaultPrevented: startPrevented} = await this._dispatchTouchEvent({
type: 'touchstart', type: 'touchstart',
modifiers, modifiers,
touchPoints: [{x, y}] touchPoints: [{x, y}]
}); });
await this._dispatchTouchEvent({ const {defaultPrevented: endPrevented} = await this._dispatchTouchEvent({
type: 'touchend', type: 'touchend',
modifiers, modifiers,
touchPoints: [{x, y}] touchPoints: [{x, y}]
}); });
} if (startPrevented || endPrevented)
return;
_getCurrentDragSession() {
const frame = this._frameTree.mainFrame(); const frame = this._frameTree.mainFrame();
const domWindow = frame?.domWindow(); const winUtils = frame.domWindow().windowUtils;
return domWindow ? dragService.getCurrentSession(domWindow) : undefined; winUtils.jugglerSendMouseEvent(
'mousemove',
x,
y,
0 /*button*/,
0 /*clickCount*/,
modifiers,
false /*aIgnoreRootScrollFrame*/,
0.0 /*pressure*/,
5 /*inputSource*/,
true /*isDOMEventSynthesized*/,
false /*isWidgetEventSynthesized*/,
0 /*buttons*/,
winUtils.DEFAULT_MOUSE_POINTER_ID /* pointerIdentifier */,
true /*disablePointerEvent*/
);
winUtils.jugglerSendMouseEvent(
'mousedown',
x,
y,
0 /*button*/,
1 /*clickCount*/,
modifiers,
false /*aIgnoreRootScrollFrame*/,
0.0 /*pressure*/,
5 /*inputSource*/,
true /*isDOMEventSynthesized*/,
false /*isWidgetEventSynthesized*/,
1 /*buttons*/,
winUtils.DEFAULT_MOUSE_POINTER_ID /*pointerIdentifier*/,
true /*disablePointerEvent*/,
);
winUtils.jugglerSendMouseEvent(
'mouseup',
x,
y,
0 /*button*/,
1 /*clickCount*/,
modifiers,
false /*aIgnoreRootScrollFrame*/,
0.0 /*pressure*/,
5 /*inputSource*/,
true /*isDOMEventSynthesized*/,
false /*isWidgetEventSynthesized*/,
0 /*buttons*/,
winUtils.DEFAULT_MOUSE_POINTER_ID /*pointerIdentifier*/,
true /*disablePointerEvent*/,
);
} }
async _dispatchDragEvent({type, x, y, modifiers}) { async _dispatchDragEvent({type, x, y, modifiers}) {
const session = this._getCurrentDragSession(); const session = dragService.getCurrentSession();
const dropEffect = session.dataTransfer.dropEffect; const dropEffect = session.dataTransfer.dropEffect;
if ((type === 'drop' && dropEffect !== 'none') || type === 'dragover') { if ((type === 'drop' && dropEffect !== 'none') || type === 'dragover') {
@ -558,8 +611,9 @@ class PageAgent {
return; return;
} }
if (type === 'dragend') { if (type === 'dragend') {
const session = this._getCurrentDragSession(); const session = dragService.getCurrentSession();
session?.endDragSession(true); if (session)
dragService.endDragSession(true);
return; return;
} }
} }

View file

@ -7,10 +7,24 @@ const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTr
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js'); const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js'); const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
const browsingContextToAgents = new Map();
const helper = new Helper(); const helper = new Helper();
function initialize(browsingContext, docShell) { function initialize(browsingContext, docShell, actor) {
const data = { channel: undefined, pageAgent: undefined, frameTree: undefined, failedToOverrideTimezone: false }; if (browsingContext.parent) {
// For child frames, return agents from the main frame.
return browsingContextToAgents.get(browsingContext.top);
}
let data = browsingContextToAgents.get(browsingContext);
if (data) {
// Rebind from one main frame actor to another one.
data.channel.bindToActor(actor);
return data;
}
data = { channel: undefined, pageAgent: undefined, frameTree: undefined, failedToOverrideTimezone: false };
browsingContextToAgents.set(browsingContext, data);
const applySetting = { const applySetting = {
geolocation: (geolocation) => { geolocation: (geolocation) => {
@ -45,6 +59,10 @@ function initialize(browsingContext, docShell) {
docShell.languageOverride = locale; docShell.languageOverride = locale;
}, },
scrollbarsHidden: (hidden) => {
data.frameTree.setScrollbarsHidden(hidden);
},
javaScriptDisabled: (javaScriptDisabled) => { javaScriptDisabled: (javaScriptDisabled) => {
data.frameTree.setJavaScriptDisabled(javaScriptDisabled); data.frameTree.setJavaScriptDisabled(javaScriptDisabled);
}, },
@ -66,6 +84,7 @@ function initialize(browsingContext, docShell) {
data.frameTree.addBinding(worldName, name, script); data.frameTree.addBinding(worldName, name, script);
data.frameTree.setInitScripts([...contextCrossProcessCookie.initScripts, ...pageCrossProcessCookie.initScripts]); data.frameTree.setInitScripts([...contextCrossProcessCookie.initScripts, ...pageCrossProcessCookie.initScripts]);
data.channel = new SimpleChannel('', 'process-' + Services.appinfo.processID); data.channel = new SimpleChannel('', 'process-' + Services.appinfo.processID);
data.channel.bindToActor(actor);
data.pageAgent = new PageAgent(data.channel, data.frameTree); data.pageAgent = new PageAgent(data.channel, data.frameTree);
docShell.fileInputInterceptionEnabled = !!pageCrossProcessCookie.interceptFileChooserDialog; docShell.fileInputInterceptionEnabled = !!pageCrossProcessCookie.interceptFileChooserDialog;

View file

@ -186,10 +186,6 @@ class BrowserHandler {
this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled; this._targetRegistry.browserContextForId(browserContextId).requestInterceptionEnabled = enabled;
} }
['Browser.setCacheDisabled']({browserContextId, cacheDisabled}) {
this._targetRegistry.browserContextForId(browserContextId).setCacheDisabled(cacheDisabled);
}
['Browser.setIgnoreHTTPSErrors']({browserContextId, ignoreHTTPSErrors}) { ['Browser.setIgnoreHTTPSErrors']({browserContextId, ignoreHTTPSErrors}) {
this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors)); this._targetRegistry.browserContextForId(browserContextId).setIgnoreHTTPSErrors(nullToUndefined(ignoreHTTPSErrors));
} }
@ -255,6 +251,10 @@ class BrowserHandler {
await this._targetRegistry.browserContextForId(browserContextId).setDefaultViewport(nullToUndefined(viewport)); 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}) { async ['Browser.setInitScripts']({browserContextId, scripts}) {
await this._targetRegistry.browserContextForId(browserContextId).setInitScripts(scripts); await this._targetRegistry.browserContextForId(browserContextId).setInitScripts(scripts);
} }

View file

@ -256,13 +256,6 @@ class PageHandler {
return await this._contentPage.send('disposeObject', options); 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}) { async ['Network.getResponseBody']({requestId}) {
return this._pageNetwork.getResponseBody(requestId); return this._pageNetwork.getResponseBody(requestId);
} }
@ -309,8 +302,8 @@ class PageHandler {
await this._pageTarget.activateAndRun(() => {}); await this._pageTarget.activateAndRun(() => {});
} }
async ['Page.setCacheDisabled']({cacheDisabled}) { async ['Page.setCacheDisabled'](options) {
return await this._pageTarget.setCacheDisabled(cacheDisabled); return await this._contentPage.send('setCacheDisabled', options);
} }
async ['Page.addBinding']({ worldName, name, script }) { async ['Page.addBinding']({ worldName, name, script }) {

View file

@ -322,12 +322,6 @@ const Browser = {
enabled: t.Boolean, enabled: t.Boolean,
}, },
}, },
'setCacheDisabled': {
params: {
browserContextId: t.Optional(t.String),
cacheDisabled: t.Boolean,
},
},
'setGeolocationOverride': { 'setGeolocationOverride': {
params: { params: {
browserContextId: t.Optional(t.String), browserContextId: t.Optional(t.String),
@ -394,6 +388,12 @@ const Browser = {
viewport: t.Nullable(pageTypes.Viewport), viewport: t.Nullable(pageTypes.Viewport),
} }
}, },
'setScrollbarsHidden': {
params: {
browserContextId: t.Optional(t.String),
hidden: t.Boolean,
}
},
'setInitScripts': { 'setInitScripts': {
params: { params: {
browserContextId: t.Optional(t.String), browserContextId: t.Optional(t.String),
@ -481,17 +481,6 @@ const Browser = {
}, },
}; };
const Heap = {
targets: ['page'],
types: {},
events: {},
methods: {
'collectGarbage': {
params: {},
},
},
};
const Network = { const Network = {
targets: ['page'], targets: ['page'],
types: networkTypes, types: networkTypes,
@ -1007,7 +996,7 @@ const Accessibility = {
} }
this.protocol = { this.protocol = {
domains: {Browser, Heap, Page, Runtime, Network, Accessibility}, domains: {Browser, Page, Runtime, Network, Accessibility},
}; };
this.checkScheme = checkScheme; this.checkScheme = checkScheme;
this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme']; this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme'];

View file

@ -129,7 +129,7 @@ class nsScreencastService::Session : public rtc::VideoSinkInterface<webrtc::Vide
capability.height = 960; capability.height = 960;
capability.maxFPS = ScreencastEncoder::fps; capability.maxFPS = ScreencastEncoder::fps;
capability.videoType = webrtc::VideoType::kI420; capability.videoType = webrtc::VideoType::kI420;
int error = mCaptureModule->StartCaptureCounted(capability); int error = mCaptureModule->StartCapture(capability);
if (error) { if (error) {
fprintf(stderr, "StartCapture error %d\n", error); fprintf(stderr, "StartCapture error %d\n", error);
return false; return false;
@ -152,7 +152,7 @@ class nsScreencastService::Session : public rtc::VideoSinkInterface<webrtc::Vide
mCaptureModule->DeRegisterCaptureDataCallback(this); mCaptureModule->DeRegisterCaptureDataCallback(this);
else else
mCaptureModule->DeRegisterRawFrameCallback(this); mCaptureModule->DeRegisterRawFrameCallback(this);
mCaptureModule->StopCaptureCounted(); mCaptureModule->StopCapture();
if (mEncoder) { if (mEncoder) {
mEncoder->finish([this, protect = RefPtr{this}] { mEncoder->finish([this, protect = RefPtr{this}] {
NS_DispatchToMainThread(NS_NewRunnableFunction( NS_DispatchToMainThread(NS_NewRunnableFunction(

File diff suppressed because it is too large Load diff

View file

@ -47,9 +47,6 @@ pref("permissions.isolateBy.userContext", true);
// |Page.setFileInputFiles| protocol method. // |Page.setFileInputFiles| protocol method.
pref("dom.file.createInChild", true); pref("dom.file.createInChild", true);
// Allow uploading directorys in content process.
pref("dom.filesystem.pathcheck.disabled", true);
// Do not warn when closing all open tabs // Do not warn when closing all open tabs
pref("browser.tabs.warnOnClose", false); pref("browser.tabs.warnOnClose", false);
@ -100,11 +97,6 @@ pref("extensions.formautofill.addresses.supported", "off");
// firefox behavior with other browser defaults. // firefox behavior with other browser defaults.
pref("security.enterprise_roots.enabled", true); 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. // Avoid stalling on shutdown, after "xpcom-will-shutdown" phase.
// This at least happens when shutting down soon after launching. // This at least happens when shutting down soon after launching.
// See AppShutdown.cpp for more details on shutdown phases. // See AppShutdown.cpp for more details on shutdown phases.

View file

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

View file

@ -33,7 +33,6 @@
#import <WebKit/WKUserContentControllerPrivate.h> #import <WebKit/WKUserContentControllerPrivate.h>
#import <WebKit/WKWebViewConfigurationPrivate.h> #import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/WKWebViewPrivate.h> #import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WKWebpagePreferencesPrivate.h>
#import <WebKit/WKWebsiteDataStorePrivate.h> #import <WebKit/WKWebsiteDataStorePrivate.h>
#import <WebKit/WebNSURLExtras.h> #import <WebKit/WebNSURLExtras.h>
#import <WebKit/WebKit.h> #import <WebKit/WebKit.h>
@ -98,7 +97,7 @@ const NSActivityOptions ActivityOptions =
for (NSString *argument in subArray) { for (NSString *argument in subArray) {
if (![argument hasPrefix:@"--"]) if (![argument hasPrefix:@"--"])
_initialURL = [argument copy]; _initialURL = argument;
if ([argument hasPrefix:@"--user-data-dir="]) { if ([argument hasPrefix:@"--user-data-dir="]) {
NSRange range = NSMakeRange(16, [argument length] - 16); NSRange range = NSMakeRange(16, [argument length] - 16);
_userDataDir = [[argument substringWithRange:range] copy]; _userDataDir = [[argument substringWithRange:range] copy];
@ -231,7 +230,7 @@ const NSActivityOptions ActivityOptions =
configuration = [[WKWebViewConfiguration alloc] init]; configuration = [[WKWebViewConfiguration alloc] init];
configuration.websiteDataStore = [self persistentDataStore]; configuration.websiteDataStore = [self persistentDataStore];
configuration._controlledByAutomation = true; configuration._controlledByAutomation = true;
configuration.preferences.elementFullscreenEnabled = YES; configuration.preferences._fullScreenEnabled = YES;
configuration.preferences._developerExtrasEnabled = YES; configuration.preferences._developerExtrasEnabled = YES;
configuration.preferences._mediaDevicesEnabled = YES; configuration.preferences._mediaDevicesEnabled = YES;
configuration.preferences._mockCaptureDevicesEnabled = YES; configuration.preferences._mockCaptureDevicesEnabled = YES;
@ -241,8 +240,6 @@ const NSActivityOptions ActivityOptions =
configuration.preferences._hiddenPageDOMTimerThrottlingAutoIncreases = NO; configuration.preferences._hiddenPageDOMTimerThrottlingAutoIncreases = NO;
configuration.preferences._pageVisibilityBasedProcessSuppressionEnabled = NO; configuration.preferences._pageVisibilityBasedProcessSuppressionEnabled = NO;
configuration.preferences._domTimersThrottlingEnabled = NO; configuration.preferences._domTimersThrottlingEnabled = NO;
// Do not auto play audio and video with sound.
configuration.defaultWebpagePreferences._autoplayPolicy = _WKWebsiteAutoplayPolicyAllowWithoutSound;
_WKProcessPoolConfiguration *processConfiguration = [[[_WKProcessPoolConfiguration alloc] init] autorelease]; _WKProcessPoolConfiguration *processConfiguration = [[[_WKProcessPoolConfiguration alloc] init] autorelease];
processConfiguration.forceOverlayScrollbars = YES; processConfiguration.forceOverlayScrollbars = YES;
configuration.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease]; configuration.processPool = [[[WKProcessPool alloc] _initWithConfiguration:processConfiguration] autorelease];

File diff suppressed because it is too large Load diff

View file

@ -39,4 +39,5 @@ fi
# create a TMP directory to copy all necessary files # create a TMP directory to copy all necessary files
cd ./x64/Release cd ./x64/Release
7z a "$ZIP_PATH" ./PrintDeps.exe zip $ZIP_PATH ./PrintDeps.exe

View file

@ -14,6 +14,8 @@ A few examples of problems this can catch include:
The following examples rely on the [`com.deque.html.axe-core/playwright`](https://mvnrepository.com/artifact/com.deque.html.axe-core/playwright) Maven package which adds support for running the [axe accessibility testing engine](https://www.deque.com/axe/) as part of your Playwright tests. The following examples rely on the [`com.deque.html.axe-core/playwright`](https://mvnrepository.com/artifact/com.deque.html.axe-core/playwright) Maven package which adds support for running the [axe accessibility testing engine](https://www.deque.com/axe/) as part of your Playwright tests.
<!-- TOC -->
## Disclaimer ## Disclaimer
Automated accessibility tests can detect some common accessibility problems such as missing or invalid properties. But many accessibility problems can only be discovered through manual testing. We recommend using a combination of automated testing, manual accessibility assessments, and inclusive user testing. Automated accessibility tests can detect some common accessibility problems such as missing or invalid properties. But many accessibility problems can only be discovered through manual testing. We recommend using a combination of automated testing, manual accessibility assessments, and inclusive user testing.
@ -70,9 +72,8 @@ 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()`: `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 ```java
public class HomepageTests { @Test
@Test void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception {
void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception {
page.navigate("https://your-site.com/"); page.navigate("https://your-site.com/");
page.locator("button[aria-label=\"Navigation Menu\"]").click(); page.locator("button[aria-label=\"Navigation Menu\"]").click();
@ -87,7 +88,6 @@ public class HomepageTests {
.analyze(); .analyze();
assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
}
} }
``` ```
@ -135,7 +135,7 @@ If the element in question is used repeatedly in many pages, consider [using a t
### Disabling individual scan rules ### Disabling individual scan rules
If your application contains many different preexisting violations of a specific rule, you can use [`AxeBuilder.disableRules()`](https://github.com/dequelabs/axe-core-maven-html/blob/develop/playwright/README.md#axebuilderdisablerulesliststring-rules) to temporarily disable individual rules until you're able to fix the issues. If your application contains many different pre-existing violations of a specific rule, you can use [`AxeBuilder.disableRules()`](https://github.com/dequelabs/axe-core-maven-html/blob/develop/playwright/README.md#axebuilderdisablerulesliststring-rules) to temporarily disable individual rules until you're able to fix the issues.
You can find the rule IDs to pass to `disableRules()` in the `id` property of the violations you want to suppress. A [complete list of axe's rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) can be found in `axe-core`'s documentation. You can find the rule IDs to pass to `disableRules()` in the `id` property of the violations you want to suppress. A [complete list of axe's rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) can be found in `axe-core`'s documentation.
@ -160,9 +160,8 @@ 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: Here is an example of using fingerprints based on only rule IDs and "target" selectors pointing to each violation:
```java ```java
public class HomepageTests { @Test
@Test shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception {
shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception {
page.navigate("https://your-site.com/"); page.navigate("https://your-site.com/");
AxeResults accessibilityScanResults = new AxeBuilder(page).analyze(); AxeResults accessibilityScanResults = new AxeBuilder(page).analyze();
@ -174,15 +173,15 @@ public class HomepageTests {
new ViolationFingerprint("color-contrast", "[li:nth-child(2) > span]"), new ViolationFingerprint("color-contrast", "[li:nth-child(2) > span]"),
new ViolationFingerprint("label", "[input]") new ViolationFingerprint("label", "[input]")
), violationFingerprints); ), violationFingerprints);
} }
// You can make your "fingerprint" as specific as you like. This one considers a violation to be // You can make your "fingerprint" as specific as you like. This one considers a violation to be
// "the same" if it corresponds the same Axe rule on the same element. // "the same" if it corresponds the same Axe rule on the same element.
// //
// Using a record type makes it easy to compare fingerprints with assertEquals // Using a record type makes it easy to compare fingerprints with assertEquals
public record ViolationFingerprint(String ruleId, String target) { } public record ViolationFingerprint(String ruleId, String target) { }
public List<ViolationFingerprint> fingerprintsFromScanResults(AxeResults results) { public List<ViolationFingerprint> fingerprintsFromScanResults(AxeResults results) {
return results.getViolations().stream() return results.getViolations().stream()
// Each violation refers to one rule and multiple "nodes" which violate it // Each violation refers to one rule and multiple "nodes" which violate it
.flatMap(violation -> violation.getNodes().stream() .flatMap(violation -> violation.getNodes().stream()
@ -193,7 +192,6 @@ public class HomepageTests {
node.getTarget().toString() node.getTarget().toString()
))) )))
.collect(Collectors.toList()); .collect(Collectors.toList());
}
} }
``` ```
@ -214,8 +212,8 @@ This example fixture creates an `AxeBuilder` object which is pre-configured with
class AxeTestFixtures extends TestFixtures { class AxeTestFixtures extends TestFixtures {
AxeBuilder makeAxeBuilder() { AxeBuilder makeAxeBuilder() {
return new AxeBuilder(page) return new AxeBuilder(page)
.withTags(new String[]{"wcag2a", "wcag2aa", "wcag21a", "wcag21aa"}) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.exclude("#commonly-reused-element-with-known-issue"); .exclude('#commonly-reused-element-with-known-issue');
} }
} }
``` ```
@ -233,7 +231,7 @@ public class HomepageTests extends AxeTestFixtures {
AxeResults accessibilityScanResults = makeAxeBuilder() AxeResults accessibilityScanResults = makeAxeBuilder()
// Automatically uses the shared AxeBuilder configuration, // Automatically uses the shared AxeBuilder configuration,
// but supports additional test-specific configuration too // but supports additional test-specific configuration too
.include("#specific-element-under-test") .include('#specific-element-under-test')
.analyze(); .analyze();
assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

View file

@ -147,7 +147,7 @@ If the element in question is used repeatedly in many pages, consider [using a t
### Disabling individual scan rules ### Disabling individual scan rules
If your application contains many different preexisting violations of a specific rule, you can use [`AxeBuilder.disableRules()`](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md#axebuilderdisablerulesrules-stringarray) to temporarily disable individual rules until you're able to fix the issues. If your application contains many different pre-existing violations of a specific rule, you can use [`AxeBuilder.disableRules()`](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md#axebuilderdisablerulesrules-stringarray) to temporarily disable individual rules until you're able to fix the issues.
You can find the rule IDs to pass to `disableRules()` in the `id` property of the violations you want to suppress. A [complete list of axe's rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) can be found in `axe-core`'s documentation. You can find the rule IDs to pass to `disableRules()` in the `id` property of the violations you want to suppress. A [complete list of axe's rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) can be found in `axe-core`'s documentation.
@ -167,7 +167,7 @@ test('should not have any accessibility violations outside of rules with known i
### Using snapshots to allow specific known issues ### Using snapshots to allow specific known issues
If you would like to allow for a more granular set of known issues, you can use [Snapshots](./test-snapshots.md) to verify that a set of preexisting violations has not changed. This approach avoids the downsides of using `AxeBuilder.exclude()` at the cost of slightly more complexity and fragility. If you would like to allow for a more granular set of known issues, you can use [Snapshots](./test-snapshots.md) to verify that a set of pre-existing violations has not changed. This approach avoids the downsides of using `AxeBuilder.exclude()` at the cost of slightly more complexity and fragility.
Do not use a snapshot of the entire `accessibilityScanResults.violations` array. It contains implementation details of the elements in question, such as a snippet of their rendered HTML; if you include these in your snapshots, it will make your tests prone to breaking every time one of the components in question changes for an unrelated reason: Do not use a snapshot of the entire `accessibilityScanResults.violations` array. It contains implementation details of the elements in question, such as a snippet of their rendered HTML; if you include these in your snapshots, it will make your tests prone to breaking every time one of the components in question changes for an unrelated reason:

View file

@ -9,7 +9,7 @@ Playwright performs a range of actionability checks on the elements before makin
behave as expected. It auto-waits for all the relevant checks to pass and only then performs the requested action. If the required checks do not pass within the given `timeout`, action fails with the `TimeoutError`. behave as expected. It auto-waits for all the relevant checks to pass and only then performs the requested action. If the required checks do not pass within the given `timeout`, action fails with the `TimeoutError`.
For example, for [`method: Locator.click`], Playwright will ensure that: For example, for [`method: Locator.click`], Playwright will ensure that:
- locator resolves to exactly one element - locator resolves to an exactly one element
- element is [Visible] - element is [Visible]
- element is [Stable], as in not animating or completed animation - element is [Stable], as in not animating or completed animation
- element [Receives Events], as in not obscured by other elements - element [Receives Events], as in not obscured by other elements
@ -93,20 +93,11 @@ Element is considered stable when it has maintained the same bounding box for at
## Enabled ## Enabled
Element is considered enabled when it is **not disabled**. Element is considered enabled unless it is a `<button>`, `<select>`, `<input>` or `<textarea>` with a `disabled` property.
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.
## Editable ## Editable
Element is considered editable when it is [enabled] and is **not readonly**. Element is considered editable when it is [enabled] and does not have `readonly` property set.
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).
## Receives Events ## Receives Events

View file

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

View file

@ -16,6 +16,8 @@ A few examples where it may come in handy:
All of that could be achieved via [APIRequestContext] methods. All of that could be achieved via [APIRequestContext] methods.
<!-- TOC -->
## Writing API Test ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [APIRequestContext] can send all kinds of HTTP(S) requests over network.
@ -194,7 +196,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. 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 ```java
public class TestGitHubAPI {
// ... // ...
void createTestRepository() { void createTestRepository() {
@ -224,7 +225,6 @@ public class TestGitHubAPI {
disposeAPIRequestContext(); disposeAPIRequestContext();
closePlaywright(); closePlaywright();
} }
}
``` ```
### Complete test example ### Complete test example
@ -383,9 +383,8 @@ 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]. project to check that it appears at the top of the list. The check is performed using [LocatorAssertions].
```java ```java
public class TestGitHubAPI { @Test
@Test void lastCreatedIssueShouldBeFirstInTheList() {
void lastCreatedIssueShouldBeFirstInTheList() {
Map<String, String> data = new HashMap<>(); Map<String, String> data = new HashMap<>();
data.put("title", "[Feature] request 1"); data.put("title", "[Feature] request 1");
data.put("body", "Feature description"); data.put("body", "Feature description");
@ -396,7 +395,6 @@ public class TestGitHubAPI {
page.navigate("https://github.com/" + USER + "/" + REPO + "/issues"); page.navigate("https://github.com/" + USER + "/" + REPO + "/issues");
Locator firstIssue = page.locator("a[data-hovercard-type='issue']").first(); Locator firstIssue = page.locator("a[data-hovercard-type='issue']").first();
assertThat(firstIssue).hasText("[Feature] request 1"); assertThat(firstIssue).hasText("[Feature] request 1");
}
} }
``` ```
@ -406,9 +404,8 @@ The following test creates a new issue via user interface in the browser and the
it was created: it was created:
```java ```java
public class TestGitHubAPI { @Test
@Test void lastCreatedIssueShouldBeOnTheServer() {
void lastCreatedIssueShouldBeOnTheServer() {
page.navigate("https://github.com/" + USER + "/" + REPO + "/issues"); page.navigate("https://github.com/" + USER + "/" + REPO + "/issues");
page.locator("text=New Issue").click(); page.locator("text=New Issue").click();
page.locator("[aria-label='Title']").fill("Bug report 1"); page.locator("[aria-label='Title']").fill("Bug report 1");
@ -419,7 +416,6 @@ public class TestGitHubAPI {
APIResponse newIssue = request.get("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId); APIResponse newIssue = request.get("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId);
assertThat(newIssue).isOK(); assertThat(newIssue).isOK();
assertTrue(newIssue.text().contains("Bug report 1")); assertTrue(newIssue.text().contains("Bug report 1"));
}
} }
``` ```

View file

@ -16,6 +16,8 @@ A few examples where it may come in handy:
All of that could be achieved via [APIRequestContext] methods. All of that could be achieved via [APIRequestContext] methods.
<!-- TOC3 -->
## Writing API Test ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [APIRequestContext] can send all kinds of HTTP(S) requests over network.

View file

@ -18,6 +18,8 @@ All of that could be achieved via [APIRequestContext] methods.
The following examples rely on the [`pytest-playwright`](./test-runners.md) package which add Playwright fixtures to the Pytest test-runner. The following examples rely on the [`pytest-playwright`](./test-runners.md) package which add Playwright fixtures to the Pytest test-runner.
<!-- TOC -->
## Writing API Test ## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network. [APIRequestContext] can send all kinds of HTTP(S) requests over network.

View file

@ -202,12 +202,6 @@ Prevents automatic playwright driver installation on attach. Assumes that the dr
Optional device serial number to launch the browser on. If not specified, it will Optional device serial number to launch the browser on. If not specified, it will
throw if multiple devices are connected. throw if multiple devices are connected.
### option: Android.launchServer.host
* since: v1.45
- `host` <[string]>
Host to use for the web socket. It is optional and if it is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. Consider hardening it with picking a specific interface.
### option: Android.launchServer.port ### option: Android.launchServer.port
* since: v1.28 * since: v1.28
- `port` <[int]> - `port` <[int]>

View file

@ -136,7 +136,7 @@ Launches Chrome browser on the device, and returns its persistent context.
### option: AndroidDevice.launchBrowser.pkg ### option: AndroidDevice.launchBrowser.pkg
* since: v1.9 * since: v1.9
- `pkg` <[string]> - `command` <[string]>
Optional package name to launch instead of default Chrome for Android. 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 ### param: AndroidDevice.open.command
* since: v1.9 * since: v1.9
- `command` <[string]> - `command` <[string]> Shell command to execute.
Shell command to execute.
## async method: AndroidDevice.pinchClose ## async method: AndroidDevice.pinchClose
* since: v1.9 * since: v1.9
@ -447,7 +445,7 @@ Either a predicate that receives an event or an options object. Optional.
* since: v1.9 * since: v1.9
- returns: <[AndroidWebView]> - 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 ### param: AndroidDevice.webView.selector
* since: v1.9 * since: v1.9

View file

@ -12,22 +12,12 @@ see [APIRequestContext].
Creates new instances of [APIRequestContext]. Creates new instances of [APIRequestContext].
### option: APIRequest.newContext.clientCertificates = %%-context-option-clientCertificates-%%
* since: 1.46
### option: APIRequest.newContext.useragent = %%-context-option-useragent-%% ### option: APIRequest.newContext.useragent = %%-context-option-useragent-%%
* since: v1.16 * since: v1.16
### option: APIRequest.newContext.extraHTTPHeaders = %%-context-option-extrahttpheaders-%% ### option: APIRequest.newContext.extraHTTPHeaders = %%-context-option-extrahttpheaders-%%
* since: v1.16 * 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-%% ### option: APIRequest.newContext.httpCredentials = %%-context-option-httpcredentials-%%
* since: v1.16 * since: v1.16
@ -71,7 +61,6 @@ Methods like [`method: APIRequestContext.get`] take the base URL into considerat
- `localStorage` <[Array]<[Object]>> - `localStorage` <[Array]<[Object]>>
- `name` <[string]> - `name` <[string]>
- `value` <[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 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 obtained via [`method: BrowserContext.storageState`] or [`method: APIRequestContext.storageState`]. Either a path to the

View file

@ -138,31 +138,22 @@ context cookies from the response. The method will automatically follow redirect
### param: APIRequestContext.delete.url = %%-fetch-param-url-%% ### param: APIRequestContext.delete.url = %%-fetch-param-url-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.delete.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.delete.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.delete.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.delete.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.delete.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.delete.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.delete.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.delete.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.delete.headers = %%-js-python-csharp-fetch-option-headers-%% ### option: APIRequestContext.delete.headers = %%-js-python-csharp-fetch-option-headers-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.delete.data = %%-js-python-csharp-fetch-option-data-%% ### option: APIRequestContext.delete.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.17 * since: v1.17
### option: APIRequestContext.delete.form = %%-js-fetch-option-form-%% ### option: APIRequestContext.delete.form = %%-js-python-fetch-option-form-%%
* since: v1.17
### option: APIRequestContext.delete.form = %%-python-fetch-option-form-%%
* since: v1.17 * since: v1.17
### option: APIRequestContext.delete.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.delete.form = %%-csharp-fetch-option-form-%%
@ -189,20 +180,11 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.delete.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.delete.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.delete.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.dispose ## async method: APIRequestContext.dispose
* since: v1.16 * since: v1.16
All responses returned by [`method: APIRequestContext.get`] and similar methods are stored in the memory, so that you can later call [`method: APIResponse.body`].This method discards all its resources, calling any method on disposed [APIRequestContext] will throw an exception. All responses returned by [`method: APIRequestContext.get`] and similar methods are stored in the memory, so that you can later call [`method: APIResponse.body`].This method discards all its resources, calling any method on disposed [APIRequestContext] will throw an exception.
### option: APIRequestContext.dispose.reason
* since: v1.45
- `reason` <[string]>
The reason to be reported to the operations interrupted by the context disposal.
## async method: APIRequestContext.fetch ## async method: APIRequestContext.fetch
* since: v1.16 * since: v1.16
- returns: <[APIResponse]> - returns: <[APIResponse]>
@ -247,7 +229,7 @@ var data = new Dictionary<string, object>() {
await Request.FetchAsync("https://example.com/api/createBook", new() { Method = "post", DataObject = data }); 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 ```js
const form = new FormData(); const form = new FormData();
@ -300,28 +282,21 @@ multipart.Set("fileField", file);
await Request.FetchAsync("https://example.com/api/uploadScript", new() { Method = "post", Multipart = multipart }); await Request.FetchAsync("https://example.com/api/uploadScript", new() { Method = "post", Multipart = multipart });
``` ```
### param: APIRequestContext.fetch.urlOrRequest ### param: APIRequestContext.fetch.urlOrRequest
* since: v1.16 * since: v1.16
- `urlOrRequest` <[string]|[Request]> - `urlOrRequest` <[string]|[Request]>
Target URL or Request to get all parameters from. Target URL or Request to get all parameters from.
### option: APIRequestContext.fetch.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.fetch.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.fetch.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.fetch.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.fetch.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.fetch.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.fetch.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.fetch.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.fetch.method ### option: APIRequestContext.fetch.method
* since: v1.16 * since: v1.16
* langs: js, python, csharp * langs: js, python, csharp
@ -336,10 +311,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-%% ### option: APIRequestContext.fetch.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.fetch.form = %%-js-fetch-option-form-%% ### option: APIRequestContext.fetch.form = %%-js-python-fetch-option-form-%%
* since: v1.16
### option: APIRequestContext.fetch.form = %%-python-fetch-option-form-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.fetch.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.fetch.form = %%-csharp-fetch-option-form-%%
@ -366,9 +338,6 @@ If set changes the fetch method (e.g. [PUT](https://developer.mozilla.org/en-US/
### option: APIRequestContext.fetch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.fetch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.fetch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.get ## async method: APIRequestContext.get
* since: v1.16 * since: v1.16
- returns: <[APIResponse]> - returns: <[APIResponse]>
@ -382,24 +351,12 @@ context cookies from the response. The method will automatically follow redirect
Request parameters can be configured with `params` option, they will be serialized into the URL search parameters: Request parameters can be configured with `params` option, they will be serialized into the URL search parameters:
```js ```js
// Passing params as object
await request.get('https://example.com/api/getText', { await request.get('https://example.com/api/getText', {
params: { params: {
'isbn': '1234', 'isbn': '1234',
'page': 23, 'page': 23,
} }
}); });
// Passing params as URLSearchParams
const searchParams = new URLSearchParams();
searchParams.set('isbn', '1234');
searchParams.append('page', 23);
searchParams.append('page', 24);
await request.get('https://example.com/api/getText', { params: searchParams });
// Passing params as string
const queryString = 'isbn=1234&page=23&page=24';
await request.get('https://example.com/api/getText', { params: queryString });
``` ```
```java ```java
@ -428,31 +385,22 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
### param: APIRequestContext.get.url = %%-fetch-param-url-%% ### param: APIRequestContext.get.url = %%-fetch-param-url-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.get.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.get.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.get.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.get.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.get.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.get.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.get.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.get.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.get.headers = %%-js-python-csharp-fetch-option-headers-%% ### option: APIRequestContext.get.headers = %%-js-python-csharp-fetch-option-headers-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.get.data = %%-js-python-csharp-fetch-option-data-%% ### option: APIRequestContext.get.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.get.form = %%-js-fetch-option-form-%% ### option: APIRequestContext.get.form = %%-js-python-fetch-option-form-%%
* since: v1.26
### option: APIRequestContext.get.form = %%-python-fetch-option-form-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.get.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.get.form = %%-csharp-fetch-option-form-%%
@ -479,9 +427,6 @@ await request.GetAsync("https://example.com/api/getText", new() { Params = query
### option: APIRequestContext.get.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.get.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.get.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.head ## async method: APIRequestContext.head
* since: v1.16 * since: v1.16
- returns: <[APIResponse]> - returns: <[APIResponse]>
@ -493,31 +438,22 @@ context cookies from the response. The method will automatically follow redirect
### param: APIRequestContext.head.url = %%-fetch-param-url-%% ### param: APIRequestContext.head.url = %%-fetch-param-url-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.head.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.head.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.head.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.head.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.head.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.head.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.head.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.head.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.head.headers = %%-js-python-csharp-fetch-option-headers-%% ### option: APIRequestContext.head.headers = %%-js-python-csharp-fetch-option-headers-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.head.data = %%-js-python-csharp-fetch-option-data-%% ### option: APIRequestContext.head.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.head.form = %%-python-fetch-option-form-%% ### option: APIRequestContext.head.form = %%-js-python-fetch-option-form-%%
* since: v1.26
### option: APIRequestContext.head.form = %%-js-fetch-option-form-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.head.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.head.form = %%-csharp-fetch-option-form-%%
@ -544,9 +480,6 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.head.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.head.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.head.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.patch ## async method: APIRequestContext.patch
* since: v1.16 * since: v1.16
- returns: <[APIResponse]> - returns: <[APIResponse]>
@ -558,31 +491,22 @@ context cookies from the response. The method will automatically follow redirect
### param: APIRequestContext.patch.url = %%-fetch-param-url-%% ### param: APIRequestContext.patch.url = %%-fetch-param-url-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.patch.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.patch.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.patch.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.patch.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.patch.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.patch.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.patch.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.patch.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.patch.headers = %%-js-python-csharp-fetch-option-headers-%% ### option: APIRequestContext.patch.headers = %%-js-python-csharp-fetch-option-headers-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.patch.data = %%-js-python-csharp-fetch-option-data-%% ### option: APIRequestContext.patch.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.patch.form = %%-js-fetch-option-form-%% ### option: APIRequestContext.patch.form = %%-js-python-fetch-option-form-%%
* since: v1.16
### option: APIRequestContext.patch.form = %%-python-fetch-option-form-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.patch.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.patch.form = %%-csharp-fetch-option-form-%%
@ -609,9 +533,6 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.patch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.patch.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.patch.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.post ## async method: APIRequestContext.post
* since: v1.16 * since: v1.16
- returns: <[APIResponse]> - returns: <[APIResponse]>
@ -744,31 +665,22 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
### param: APIRequestContext.post.url = %%-fetch-param-url-%% ### param: APIRequestContext.post.url = %%-fetch-param-url-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.post.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.post.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.post.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.post.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.post.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.post.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.post.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.post.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.post.headers = %%-js-python-csharp-fetch-option-headers-%% ### option: APIRequestContext.post.headers = %%-js-python-csharp-fetch-option-headers-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.post.data = %%-js-python-csharp-fetch-option-data-%% ### option: APIRequestContext.post.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.post.form = %%-js-fetch-option-form-%% ### option: APIRequestContext.post.form = %%-js-python-fetch-option-form-%%
* since: v1.16
### option: APIRequestContext.post.form = %%-python-fetch-option-form-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.post.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.post.form = %%-csharp-fetch-option-form-%%
@ -795,9 +707,6 @@ await request.PostAsync("https://example.com/api/uploadScript", new() { Multipar
### option: APIRequestContext.post.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.post.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.post.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.put ## async method: APIRequestContext.put
* since: v1.16 * since: v1.16
- returns: <[APIResponse]> - returns: <[APIResponse]>
@ -809,31 +718,22 @@ context cookies from the response. The method will automatically follow redirect
### param: APIRequestContext.put.url = %%-fetch-param-url-%% ### param: APIRequestContext.put.url = %%-fetch-param-url-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.put.params = %%-js-fetch-option-params-%% ### param: APIRequestContext.put.params = %%-java-csharp-fetch-params-%%
* since: v1.16
### param: APIRequestContext.put.params = %%-java-fetch-params-%%
* since: v1.18 * since: v1.18
### option: APIRequestContext.put.params = %%-python-fetch-option-params-%% ### option: APIRequestContext.put.params = %%-js-python-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.put.params = %%-csharp-fetch-option-params-%% ### option: APIRequestContext.put.params = %%-csharp-fetch-option-params-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.put.paramsString = %%-csharp-fetch-option-paramsString-%%
* since: v1.47
### option: APIRequestContext.put.headers = %%-js-python-csharp-fetch-option-headers-%% ### option: APIRequestContext.put.headers = %%-js-python-csharp-fetch-option-headers-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.put.data = %%-js-python-csharp-fetch-option-data-%% ### option: APIRequestContext.put.data = %%-js-python-csharp-fetch-option-data-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.put.form = %%-python-fetch-option-form-%% ### option: APIRequestContext.put.form = %%-js-python-fetch-option-form-%%
* since: v1.16
### option: APIRequestContext.put.form = %%-js-fetch-option-form-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.put.form = %%-csharp-fetch-option-form-%% ### option: APIRequestContext.put.form = %%-csharp-fetch-option-form-%%
@ -860,9 +760,6 @@ context cookies from the response. The method will automatically follow redirect
### option: APIRequestContext.put.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%% ### option: APIRequestContext.put.maxRedirects = %%-js-python-csharp-fetch-option-maxredirects-%%
* since: v1.26 * since: v1.26
### option: APIRequestContext.put.maxRetries = %%-js-python-csharp-fetch-option-maxretries-%%
* since: v1.46
## async method: APIRequestContext.storageState ## async method: APIRequestContext.storageState
* since: v1.16 * since: v1.16
- returns: <[Object]> - returns: <[Object]>
@ -880,7 +777,6 @@ context cookies from the response. The method will automatically follow redirect
- `localStorage` <[Array]<[Object]>> - `localStorage` <[Array]<[Object]>>
- `name` <[string]> - `name` <[string]>
- `value` <[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. Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to the constructor.
@ -891,9 +787,3 @@ Returns storage state for this request context, contains current cookies and loc
### option: APIRequestContext.storageState.path = %%-storagestate-option-path-%% ### option: APIRequestContext.storageState.path = %%-storagestate-option-path-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.storageState.indexedDB
* since: v1.51
- `indexedDB` ?<boolean>
Set to `true` to include IndexedDB in the storage state snapshot.

View file

@ -60,7 +60,7 @@ An object with all the response HTTP headers associated with this response.
- `name` <[string]> Name of the header. - `name` <[string]> Name of the header.
- `value` <[string]> Value of the header. - `value` <[string]> Value of the header.
An array with all the response HTTP headers associated with this response. Header names are not lower-cased. An array with all the request HTTP headers associated with this response. Header names are not lower-cased.
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times. Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
## async method: APIResponse.json ## async method: APIResponse.json

View file

@ -14,15 +14,15 @@ test('navigates to login', async ({ page }) => {
``` ```
```java ```java
// ... ...
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestPage { public class TestPage {
// ... ...
@Test @Test
void navigatesToLoginPage() { void navigatesToLoginPage() {
// ... ...
APIResponse response = page.request().get("https://playwright.dev"); APIResponse response = page.request().get('https://playwright.dev');
assertThat(response).isOK(); assertThat(response).isOK();
} }
} }

View file

@ -1,5 +1,6 @@
# class: Browser # class: Browser
* since: v1.8 * since: v1.8
* extends: [EventEmitter]
A Browser is created via [`method: BrowserType.launch`]. An example of using a [Browser] to create a [Page]: A Browser is created via [`method: BrowserType.launch`]. An example of using a [Browser] to create a [Page]:
@ -20,10 +21,10 @@ import com.microsoft.playwright.*;
public class Example { public class Example {
public static void main(String[] args) { public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) { try (Playwright playwright = Playwright.create()) {
BrowserType firefox = playwright.firefox(); BrowserType firefox = playwright.firefox()
Browser browser = firefox.launch(); Browser browser = firefox.launch();
Page page = browser.newPage(); Page page = browser.newPage();
page.navigate("https://example.com"); page.navigate('https://example.com');
browser.close(); browser.close();
} }
} }
@ -96,7 +97,7 @@ In case this browser is connected to, clears all created contexts belonging to t
browser server. browser server.
:::note :::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. The [Browser] object itself is considered to be disposed and cannot be used anymore.
@ -132,16 +133,16 @@ System.out.println(browser.contexts().size()); // prints "1"
```python async ```python async
browser = await pw.webkit.launch() browser = await pw.webkit.launch()
print(len(browser.contexts)) # prints `0` print(len(browser.contexts())) # prints `0`
context = await browser.new_context() context = await browser.new_context()
print(len(browser.contexts)) # prints `1` print(len(browser.contexts())) # prints `1`
``` ```
```python sync ```python sync
browser = pw.webkit.launch() browser = pw.webkit.launch()
print(len(browser.contexts)) # prints `0` print(len(browser.contexts())) # prints `0`
context = browser.new_context() context = browser.new_context()
print(len(browser.contexts)) # prints `1` print(len(browser.contexts())) # prints `1`
``` ```
```csharp ```csharp
@ -202,7 +203,7 @@ Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'.
BrowserContext context = browser.newContext(); BrowserContext context = browser.newContext();
// Create a new page in a pristine context. // Create a new page in a pristine context.
Page page = context.newPage(); Page page = context.newPage();
page.navigate("https://example.com"); page.navigate('https://example.com');
// Graceful close up everything // Graceful close up everything
context.close(); context.close();
@ -255,9 +256,6 @@ await browser.CloseAsync();
### option: Browser.newContext.proxy = %%-context-option-proxy-%% ### option: Browser.newContext.proxy = %%-context-option-proxy-%%
* since: v1.8 * since: v1.8
### option: Browser.newContext.clientCertificates = %%-context-option-clientCertificates-%%
* since: 1.46
### option: Browser.newContext.storageState = %%-js-python-context-option-storage-state-%% ### option: Browser.newContext.storageState = %%-js-python-context-option-storage-state-%%
* since: v1.8 * since: v1.8
@ -283,9 +281,6 @@ testing frameworks should explicitly create [`method: Browser.newContext`] follo
### option: Browser.newPage.proxy = %%-context-option-proxy-%% ### option: Browser.newPage.proxy = %%-context-option-proxy-%%
* since: v1.8 * since: v1.8
### option: Browser.newPage.clientCertificates = %%-context-option-clientCertificates-%%
* since: 1.46
### option: Browser.newPage.storageState = %%-js-python-context-option-storage-state-%% ### option: Browser.newPage.storageState = %%-js-python-context-option-storage-state-%%
* since: v1.8 * since: v1.8
@ -295,20 +290,6 @@ testing frameworks should explicitly create [`method: Browser.newContext`] follo
### option: Browser.newPage.storageStatePath = %%-csharp-java-context-option-storage-state-path-%% ### option: Browser.newPage.storageStatePath = %%-csharp-java-context-option-storage-state-path-%%
* since: v1.9 * since: v1.9
## async method: Browser.removeAllListeners
* since: v1.47
* langs: js
Removes all the listeners of the given type (or all registered listeners if no type given).
Allows to wait for async listeners to complete or to ignore subsequent errors from these listeners.
### param: Browser.removeAllListeners.type
* since: v1.47
- `type` ?<[string]>
### option: Browser.removeAllListeners.behavior = %%-remove-all-listeners-options-behavior-%%
* since: v1.47
## async method: Browser.startTracing ## async method: Browser.startTracing
* since: v1.11 * since: v1.11
* langs: java, js, python * langs: java, js, python
@ -331,7 +312,7 @@ await browser.stopTracing();
```java ```java
browser.startTracing(page, new Browser.StartTracingOptions() browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(Paths.get("trace.json"))); .setPath(Paths.get("trace.json")));
page.navigate("https://www.google.com"); page.goto('https://www.google.com');
browser.stopTracing(); browser.stopTracing();
``` ```

View file

@ -1,12 +1,13 @@
# class: BrowserContext # class: BrowserContext
* since: v1.8 * since: v1.8
* extends: [EventEmitter]
BrowserContexts provide a way to operate multiple independent browser sessions. BrowserContexts provide a way to operate multiple independent browser sessions.
If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser
context. context.
Playwright allows creating isolated non-persistent browser contexts with [`method: Browser.newContext`] method. Non-persistent browser Playwright allows creating "incognito" browser contexts with [`method: Browser.newContext`] method. "Incognito" browser
contexts don't write any browsing data to disk. contexts don't write any browsing data to disk.
```js ```js
@ -97,12 +98,6 @@ context.BackgroundPage += (_, backgroundPage) =>
``` ```
## property: BrowserContext.clock
* since: v1.45
- type: <[Clock]>
Playwright has ability to mock clock and passage of time.
## event: BrowserContext.close ## event: BrowserContext.close
* since: v1.8 * since: v1.8
- argument: <[BrowserContext]> - argument: <[BrowserContext]>
@ -356,14 +351,18 @@ await context.AddCookiesAsync(new[] { cookie1, cookie2 });
- `cookies` <[Array]<[Object]>> - `cookies` <[Array]<[Object]>>
- `name` <[string]> - `name` <[string]>
- `value` <[string]> - `value` <[string]>
- `url` ?<[string]> Either url or domain / path are required. Optional. - `url` ?<[string]> either url or domain / path are required. Optional.
- `domain` ?<[string]> For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". Either url or domain / path are required. Optional. - `domain` ?<[string]> either url or domain / path are required Optional.
- `path` ?<[string]> Either url or domain / path are required Optional. - `path` ?<[string]> either url or domain / path are required Optional.
- `expires` ?<[float]> Unix time in seconds. Optional. - `expires` ?<[float]> Unix time in seconds. Optional.
- `httpOnly` ?<[boolean]> Optional. - `httpOnly` ?<[boolean]> Optional.
- `secure` ?<[boolean]> Optional. - `secure` ?<[boolean]> Optional.
- `sameSite` ?<[SameSiteAttribute]<"Strict"|"Lax"|"None">> Optional. - `sameSite` ?<[SameSiteAttribute]<"Strict"|"Lax"|"None">> Optional.
Adds cookies to the browser context.
For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
## async method: BrowserContext.addInitScript ## async method: BrowserContext.addInitScript
* since: v1.8 * since: v1.8
@ -655,7 +654,7 @@ import com.microsoft.playwright.*;
public class Example { public class Example {
public static void main(String[] args) { public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) { try (Playwright playwright = Playwright.create()) {
BrowserType webkit = playwright.webkit(); BrowserType webkit = playwright.webkit()
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
BrowserContext context = browser.newContext(); BrowserContext context = browser.newContext();
context.exposeBinding("pageURL", (source, args) -> source.page().url()); context.exposeBinding("pageURL", (source, args) -> source.page().url());
@ -743,6 +742,83 @@ await page.SetContentAsync("<script>\n" +
await page.GetByRole(AriaRole.Button).ClickAsync(); await page.GetByRole(AriaRole.Button).ClickAsync();
``` ```
An example of passing an element handle:
```js
await context.exposeBinding('clicked', async (source, element) => {
console.log(await element.textContent());
}, { handle: true });
await page.setContent(`
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
`);
```
```java
context.exposeBinding("clicked", (source, args) -> {
ElementHandle element = (ElementHandle) args[0];
System.out.println(element.textContent());
return null;
}, new BrowserContext.ExposeBindingOptions().setHandle(true));
page.setContent("" +
"<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
```
```python async
async def print(source, element):
print(await element.text_content())
await context.expose_binding("clicked", print, handle=true)
await page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```python sync
def print(source, element):
print(element.text_content())
context.expose_binding("clicked", print, handle=true)
page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```csharp
var result = new TaskCompletionSource<string>();
var page = await Context.NewPageAsync();
await Context.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
{
return result.TrySetResult(await t.AsElement().TextContentAsync());
});
await page.SetContentAsync("<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
await page.ClickAsync("div");
// Note: it makes sense to await the result here, because otherwise, the context
// gets closed and the binding function will throw an exception.
Assert.AreEqual("Click me", await result.Task);
```
### param: BrowserContext.exposeBinding.name ### param: BrowserContext.exposeBinding.name
* since: v1.8 * since: v1.8
- `name` <[string]> - `name` <[string]>
@ -757,7 +833,6 @@ Callback function that will be called in the Playwright's context.
### option: BrowserContext.exposeBinding.handle ### option: BrowserContext.exposeBinding.handle
* since: v1.8 * since: v1.8
* deprecated: This option will be removed in the future.
- `handle` <[boolean]> - `handle` <[boolean]>
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@ -813,9 +888,8 @@ import java.util.Base64;
public class Example { public class Example {
public static void main(String[] args) { public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) { try (Playwright playwright = Playwright.create()) {
BrowserType webkit = playwright.webkit(); BrowserType webkit = playwright.webkit()
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
BrowserContext context = browser.newContext();
context.exposeFunction("sha256", args -> { context.exposeFunction("sha256", args -> {
String text = (String) args[0]; String text = (String) args[0];
MessageDigest crypto; MessageDigest crypto;
@ -963,28 +1037,22 @@ specified.
* since: v1.8 * since: v1.8
- `permissions` <[Array]<[string]>> - `permissions` <[Array]<[string]>>
A list of permissions to grant. A permission or an array of permissions to grant. Permissions can be one of the following values:
:::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:
* `'accelerometer'`
* `'ambient-light-sensor'`
* `'background-sync'`
* `'camera'`
* `'clipboard-read'`
* `'clipboard-write'`
* `'geolocation'` * `'geolocation'`
* `'midi'`
* `'midi-sysex'` (system-exclusive midi)
* `'notifications'`
* `'camera'`
* `'microphone'`
* `'background-sync'`
* `'ambient-light-sensor'`
* `'accelerometer'`
* `'gyroscope'` * `'gyroscope'`
* `'magnetometer'` * `'magnetometer'`
* `'microphone'` * `'accessibility-events'`
* `'midi-sysex'` (system-exclusive midi) * `'clipboard-read'`
* `'midi'` * `'clipboard-write'`
* `'notifications'`
* `'payment-handler'` * `'payment-handler'`
* `'storage-access'`
### option: BrowserContext.grantPermissions.origin ### option: BrowserContext.grantPermissions.origin
* since: v1.8 * since: v1.8
@ -1021,20 +1089,6 @@ Creates a new page in the browser context.
Returns all open pages in the context. Returns all open pages in the context.
## async method: BrowserContext.removeAllListeners
* since: v1.47
* langs: js
Removes all the listeners of the given type (or all registered listeners if no type given).
Allows to wait for async listeners to complete or to ignore subsequent errors from these listeners.
### param: BrowserContext.removeAllListeners.type
* since: v1.47
- `type` ?<[string]>
### option: BrowserContext.removeAllListeners.behavior = %%-remove-all-listeners-options-behavior-%%
* since: v1.47
## property: BrowserContext.request ## property: BrowserContext.request
* since: v1.16 * since: v1.16
* langs: * langs:
@ -1204,7 +1258,7 @@ Enabling routing disables http cache.
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> - `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
A glob pattern, regex pattern or predicate receiving [URL] to match while routing. 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. it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
### param: BrowserContext.route.handler ### param: BrowserContext.route.handler
@ -1272,99 +1326,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. 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 ## method: BrowserContext.serviceWorkers
* since: v1.11 * since: v1.11
* langs: js, python * langs: js, python
@ -1412,7 +1373,7 @@ This setting will change the default maximum time for all the methods accepting
* since: v1.8 * since: v1.8
- `timeout` <[float]> - `timeout` <[float]>
Maximum time in milliseconds. Pass `0` to disable timeout. Maximum time in milliseconds
## async method: BrowserContext.setExtraHTTPHeaders ## async method: BrowserContext.setExtraHTTPHeaders
* since: v1.8 * since: v1.8
@ -1511,9 +1472,8 @@ Whether to emulate network being offline for the browser context.
- `localStorage` <[Array]<[Object]>> - `localStorage` <[Array]<[Object]>>
- `name` <[string]> - `name` <[string]>
- `value` <[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 ## async method: BrowserContext.storageState
* since: v1.8 * since: v1.8
@ -1523,17 +1483,6 @@ Returns storage state for this browser context, contains current cookies, local
### option: BrowserContext.storageState.path = %%-storagestate-option-path-%% ### option: BrowserContext.storageState.path = %%-storagestate-option-path-%%
* since: v1.8 * 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 ## property: BrowserContext.tracing
* since: v1.12 * since: v1.12
- type: <[Tracing]> - type: <[Tracing]>

View file

@ -31,5 +31,3 @@ Browser websocket url.
Browser websocket endpoint which can be used as an argument to [`method: BrowserType.connect`] to establish connection Browser websocket endpoint which can be used as an argument to [`method: BrowserType.connect`] to establish connection
to the browser. to the browser.
Note that if the listen `host` option in `launchServer` options is not specified, localhost will be output anyway, even if the actual listening address is an unspecified address.

View file

@ -89,17 +89,13 @@ class BrowserTypeExamples
* since: v1.8 * since: v1.8
- returns: <[Browser]> - returns: <[Browser]>
This method attaches Playwright to an existing browser instance created via `BrowserType.launchServer` in Node.js. 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).
:::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).
:::
### param: BrowserType.connect.wsEndpoint ### param: BrowserType.connect.wsEndpoint
* since: v1.10 * since: v1.10
- `wsEndpoint` <[string]> - `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 ### option: BrowserType.connect.headers
* since: v1.11 * 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. 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** **Usage**
```js ```js
@ -351,9 +343,6 @@ use a temporary directory instead.
### option: BrowserType.launchPersistentContext.firefoxUserPrefs2 = %%-csharp-java-browser-option-firefoxuserprefs-%% ### option: BrowserType.launchPersistentContext.firefoxUserPrefs2 = %%-csharp-java-browser-option-firefoxuserprefs-%%
* since: v1.40 * since: v1.40
### option: BrowserType.launchPersistentContext.clientCertificates = %%-context-option-clientCertificates-%%
* since: 1.46
## async method: BrowserType.launchServer ## async method: BrowserType.launchServer
* since: v1.8 * since: v1.8
* langs: js * langs: js
@ -391,12 +380,6 @@ const { chromium } = require('playwright'); // Or 'webkit' or 'firefox'.
### option: BrowserType.launchServer.logger = %%-browser-option-logger-%% ### option: BrowserType.launchServer.logger = %%-browser-option-logger-%%
* since: v1.8 * since: v1.8
### option: BrowserType.launchServer.host
* since: v1.45
- `host` <[string]>
Host to use for the web socket. It is optional and if it is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. Consider hardening it with picking a specific interface.
### option: BrowserType.launchServer.port ### option: BrowserType.launchServer.port
* since: v1.8 * since: v1.8
- `port` <[int]> - `port` <[int]>

View file

@ -1,5 +1,6 @@
# class: CDPSession # class: CDPSession
* since: v1.8 * since: v1.8
* extends: [EventEmitter]
The `CDPSession` instances are used to talk raw Chrome Devtools Protocol: The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
* protocol methods can be called with `session.send` method. * protocol methods can be called with `session.send` method.

View file

@ -1,342 +0,0 @@
# class: Clock
* since: v1.45
Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Learn more about [clock emulation](../clock.md).
Note that clock is installed for the entire [BrowserContext], so the time
in all the pages and iframes is controlled by the same clock.
## async method: Clock.fastForward
* since: v1.45
Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and
reopening it later, after given time.
**Usage**
```js
await page.clock.fastForward(1000);
await page.clock.fastForward('30:00');
```
```python async
await page.clock.fast_forward(1000)
await page.clock.fast_forward("30:00")
```
```python sync
page.clock.fast_forward(1000)
page.clock.fast_forward("30:00")
```
```java
page.clock().fastForward(1000);
page.clock().fastForward("30:00");
```
```csharp
await page.Clock.FastForwardAsync(1000);
await page.Clock.FastForwardAsync("30:00");
```
### param: Clock.fastForward.ticks
* since: v1.45
- `ticks` <[long]|[string]>
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
## async method: Clock.install
* since: v1.45
Install fake implementations for the following time-related functions:
* `Date`
* `setTimeout`
* `clearTimeout`
* `setInterval`
* `clearInterval`
* `requestAnimationFrame`
* `cancelAnimationFrame`
* `requestIdleCallback`
* `cancelIdleCallback`
* `performance`
Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [`method: Clock.runFor`] and [`method: Clock.fastForward`] for more information.
### option: Clock.install.time
* langs: js, java
* since: v1.45
- `time` <[long]|[string]|[Date]>
Time to initialize with, current system time by default.
### option: Clock.install.time
* langs: python
* since: v1.45
- `time` <[float]|[string]|[Date]>
Time to initialize with, current system time by default.
### option: Clock.install.time
* langs: csharp
* since: v1.45
- `time` <[string]|[Date]>
Time to initialize with, current system time by default.
## async method: Clock.runFor
* since: v1.45
Advance the clock, firing all the time-related callbacks.
**Usage**
```js
await page.clock.runFor(1000);
await page.clock.runFor('30:00');
```
```python async
await page.clock.run_for(1000);
await page.clock.run_for("30:00")
```
```python sync
page.clock.run_for(1000);
page.clock.run_for("30:00")
```
```java
page.clock().runFor(1000);
page.clock().runFor("30:00");
```
```csharp
await page.Clock.RunForAsync(1000);
await page.Clock.RunForAsync("30:00");
```
### param: Clock.runFor.ticks
* since: v1.45
- `ticks` <[long]|[string]>
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
## async method: Clock.pauseAt
* since: v1.45
Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers
are fired unless [`method: Clock.runFor`], [`method: Clock.fastForward`], [`method: Clock.pauseAt`] or [`method: Clock.resume`] is called.
Only fires due timers at most once.
This is equivalent to user closing the laptop lid for a while and reopening it at the specified time and
pausing.
**Usage**
```js
await page.clock.pauseAt(new Date('2020-02-02'));
await page.clock.pauseAt('2020-02-02');
```
```python async
await page.clock.pause_at(datetime.datetime(2020, 2, 2))
await page.clock.pause_at("2020-02-02")
```
```python sync
page.clock.pause_at(datetime.datetime(2020, 2, 2))
page.clock.pause_at("2020-02-02")
```
```java
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
page.clock().pauseAt(format.parse("2020-02-02"));
page.clock().pauseAt("2020-02-02");
```
```csharp
await page.Clock.PauseAtAsync(DateTime.Parse("2020-02-02"));
await page.Clock.PauseAtAsync("2020-02-02");
```
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
- `time` <[long]|[string]|[Date]>
Time to pause at.
### param: Clock.pauseAt.time
* langs: python
* since: v1.45
- `time` <[float]|[string]|[Date]>
Time to pause at.
### param: Clock.pauseAt.time
* langs: csharp
* since: v1.45
- `time` <[Date]|[string]>
Time to pause at.
## async method: Clock.resume
* since: v1.45
Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
## async method: Clock.setFixedTime
* since: v1.45
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
await page.clock.setFixedTime(Date.now());
await page.clock.setFixedTime(new Date('2020-02-02'));
await page.clock.setFixedTime('2020-02-02');
```
```python async
await page.clock.set_fixed_time(datetime.datetime.now())
await page.clock.set_fixed_time(datetime.datetime(2020, 2, 2))
await page.clock.set_fixed_time("2020-02-02")
```
```python sync
page.clock.set_fixed_time(datetime.datetime.now())
page.clock.set_fixed_time(datetime.datetime(2020, 2, 2))
page.clock.set_fixed_time("2020-02-02")
```
```java
page.clock().setFixedTime(new Date());
page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
page.clock().setFixedTime("2020-02-02");
```
```csharp
await page.Clock.SetFixedTimeAsync(DateTime.Now);
await page.Clock.SetFixedTimeAsync(new DateTime(2020, 2, 2));
await page.Clock.SetFixedTimeAsync("2020-02-02");
```
### param: Clock.setFixedTime.time
* langs: js, java
* since: v1.45
- `time` <[long]|[string]|[Date]>
Time to be set in milliseconds.
### param: Clock.setFixedTime.time
* langs: python
* since: v1.45
- `time` <[float]|[string]|[Date]>
Time to be set.
### param: Clock.setFixedTime.time
* langs: csharp
* since: v1.45
- `time` <[string]|[Date]>
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.
**Usage**
```js
await page.clock.setSystemTime(Date.now());
await page.clock.setSystemTime(new Date('2020-02-02'));
await page.clock.setSystemTime('2020-02-02');
```
```python async
await page.clock.set_system_time(datetime.datetime.now())
await page.clock.set_system_time(datetime.datetime(2020, 2, 2))
await page.clock.set_system_time("2020-02-02")
```
```python sync
page.clock.set_system_time(datetime.datetime.now())
page.clock.set_system_time(datetime.datetime(2020, 2, 2))
page.clock.set_system_time("2020-02-02")
```
```java
page.clock().setSystemTime(new Date());
page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
page.clock().setSystemTime("2020-02-02");
```
```csharp
await page.Clock.SetSystemTimeAsync(DateTime.Now);
await page.Clock.SetSystemTimeAsync(new DateTime(2020, 2, 2));
await page.Clock.SetSystemTimeAsync("2020-02-02");
```
### param: Clock.setSystemTime.time
* langs: js, java
* since: v1.45
- `time` <[long]|[string]|[Date]>
Time to be set in milliseconds.
### param: Clock.setSystemTime.time
* langs: python
* since: v1.45
- `time` <[float]|[string]|[Date]>
Time to be set.
### param: Clock.setSystemTime.time
* langs: csharp
* since: v1.45
- `time` <[string]|[Date]>
Time to be set.

View file

@ -2,7 +2,7 @@
* since: v1.8 * since: v1.8
[ConsoleMessage] objects are dispatched by page via the [`event: Page.console`] event. [ConsoleMessage] objects are dispatched by page via the [`event: Page.console`] event.
For each console message logged in the page there will be corresponding event in the Playwright For each console messages logged in the page there will be corresponding event in the Playwright
context. context.
```js ```js
@ -44,8 +44,8 @@ ConsoleMessage msg = page.waitForConsoleMessage(() -> {
}); });
// Deconstruct console.log arguments // Deconstruct console.log arguments
msg.args().get(0).jsonValue(); // hello msg.args().get(0).jsonValue() // hello
msg.args().get(1).jsonValue(); // 42 msg.args().get(1).jsonValue() // 42
``` ```
```python async ```python async

View file

@ -164,6 +164,7 @@ This method checks the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked. If not, this method throws. 1. Ensure that the element is now checked. If not, this method throws.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -177,7 +178,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.check.force = %%-input-force-%% ### option: ElementHandle.check.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.check.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.check.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.check.timeout = %%-input-timeout-%% ### option: ElementHandle.check.timeout = %%-input-timeout-%%
@ -250,6 +251,8 @@ This method double clicks the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set. Note that
if the first click of the `dblclick()` triggers a navigation event, this method will throw.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -275,7 +278,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.dblclick.force = %%-input-force-%% ### option: ElementHandle.dblclick.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.dblclick.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.dblclick.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.dblclick.timeout = %%-input-timeout-%% ### option: ElementHandle.dblclick.timeout = %%-input-timeout-%%
@ -534,7 +537,7 @@ Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
### option: ElementHandle.fill.force = %%-input-force-%% ### option: ElementHandle.fill.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: ElementHandle.fill.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.fill.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.fill.timeout = %%-input-timeout-%% ### option: ElementHandle.fill.timeout = %%-input-timeout-%%
@ -570,6 +573,7 @@ This method hovers over the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless `noWaitAfter` option is set.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -594,7 +598,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.hover.trial = %%-input-trial-%% ### option: ElementHandle.hover.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
### option: ElementHandle.hover.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.hover.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28 * since: v1.28
## async method: ElementHandle.innerHTML ## async method: ElementHandle.innerHTML
@ -785,8 +789,6 @@ completely visible as defined by
Throws when `elementHandle` does not point to an element Throws when `elementHandle` does not point to an element
[connected](https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected) to a Document or a ShadowRoot. [connected](https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected) to a Document or a ShadowRoot.
See [scrolling](../input.md#scrolling) for alternative ways to scroll.
### option: ElementHandle.scrollIntoViewIfNeeded.timeout = %%-input-timeout-%% ### option: ElementHandle.scrollIntoViewIfNeeded.timeout = %%-input-timeout-%%
* since: v1.8 * since: v1.8
@ -866,7 +868,7 @@ await handle.SelectOptionAsync(new[] {
### option: ElementHandle.selectOption.force = %%-input-force-%% ### option: ElementHandle.selectOption.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: ElementHandle.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.selectOption.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.selectOption.timeout = %%-input-timeout-%% ### option: ElementHandle.selectOption.timeout = %%-input-timeout-%%
@ -916,6 +918,7 @@ This method checks or unchecks an element by performing the following steps:
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked or unchecked. If not, this method throws. 1. Ensure that the element is now checked or unchecked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -927,7 +930,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.setChecked.force = %%-input-force-%% ### option: ElementHandle.setChecked.force = %%-input-force-%%
* since: v1.15 * since: v1.15
### option: ElementHandle.setChecked.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.setChecked.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.15 * since: v1.15
### option: ElementHandle.setChecked.position = %%-input-position-%% ### option: ElementHandle.setChecked.position = %%-input-position-%%
@ -948,7 +951,6 @@ When all steps combined have not finished during the specified [`option: timeout
Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they
are resolved relative to the current working directory. For empty array, clears the selected files. are resolved relative to the current working directory. For empty array, clears the selected files.
For inputs with a `[webkitdirectory]` attribute, only a single directory path is supported.
This method expects [ElementHandle] to point to an This method expects [ElementHandle] to point to an
[input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), targets the control instead. [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), targets the control instead.
@ -956,7 +958,7 @@ This method expects [ElementHandle] to point to an
### param: ElementHandle.setInputFiles.files = %%-input-files-%% ### param: ElementHandle.setInputFiles.files = %%-input-files-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.setInputFiles.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.setInputFiles.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.setInputFiles.timeout = %%-input-timeout-%% ### option: ElementHandle.setInputFiles.timeout = %%-input-timeout-%%
@ -973,6 +975,7 @@ This method taps the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -992,7 +995,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.tap.force = %%-input-force-%% ### option: ElementHandle.tap.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.tap.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.tap.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.tap.timeout = %%-input-timeout-%% ### option: ElementHandle.tap.timeout = %%-input-timeout-%%
@ -1033,7 +1036,7 @@ A text to type into a focused element.
Time to wait between key presses in milliseconds. Defaults to 0. Time to wait between key presses in milliseconds. Defaults to 0.
### option: ElementHandle.type.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.type.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.type.timeout = %%-input-timeout-%% ### option: ElementHandle.type.timeout = %%-input-timeout-%%
@ -1052,6 +1055,7 @@ This method checks the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now unchecked. If not, this method throws. 1. Ensure that the element is now unchecked. If not, this method throws.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -1065,7 +1069,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: ElementHandle.uncheck.force = %%-input-force-%% ### option: ElementHandle.uncheck.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.uncheck.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: ElementHandle.uncheck.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: ElementHandle.uncheck.timeout = %%-input-timeout-%% ### option: ElementHandle.uncheck.timeout = %%-input-timeout-%%

View file

@ -65,7 +65,7 @@ they are resolved relative to the current working directory. For empty array, cl
### param: FileChooser.setFiles.files = %%-input-files-%% ### param: FileChooser.setFiles.files = %%-input-files-%%
* since: v1.8 * since: v1.8
### option: FileChooser.setFiles.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: FileChooser.setFiles.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: FileChooser.setFiles.timeout = %%-input-timeout-%% ### option: FileChooser.setFiles.timeout = %%-input-timeout-%%

View file

@ -6,7 +6,7 @@ The [FormData] is used create form data that is sent via [APIRequestContext].
```java ```java
import com.microsoft.playwright.options.FormData; import com.microsoft.playwright.options.FormData;
// ... ...
FormData form = FormData.create() FormData form = FormData.create()
.set("firstName", "John") .set("firstName", "John")
.set("lastName", "Doe") .set("lastName", "Doe")
@ -28,7 +28,7 @@ the new value onto the end of the existing set of values.
```java ```java
import com.microsoft.playwright.options.FormData; import com.microsoft.playwright.options.FormData;
// ... ...
FormData form = FormData.create() FormData form = FormData.create()
// Only name and value are set. // Only name and value are set.
.append("firstName", "John") .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 ```java
import com.microsoft.playwright.options.FormData; import com.microsoft.playwright.options.FormData;
// ... ...
FormData form = FormData.create() FormData form = FormData.create()
// Only name and value are set. // Only name and value are set.
.set("firstName", "John") .set("firstName", "John")

View file

@ -154,7 +154,7 @@ Raw JavaScript content to be injected into frame.
* since: v1.8 * since: v1.8
- `type` <[string]> - `type` <[string]>
Script type. Use 'module' in order to load a JavaScript ES6 module. See Script type. Use 'module' in order to load a Javascript ES6 module. See
[script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) for more details. [script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) for more details.
## async method: Frame.addStyleTag ## async method: Frame.addStyleTag
@ -198,6 +198,7 @@ This method checks an element matching [`param: selector`] by performing the fol
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked. If not, this method throws. 1. Ensure that the element is now checked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -209,7 +210,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.check.force = %%-input-force-%% ### option: Frame.check.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: Frame.check.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.check.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.check.position = %%-input-position-%% ### option: Frame.check.position = %%-input-position-%%
@ -280,7 +281,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.click.timeout = %%-input-timeout-js-%% ### option: Frame.click.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Frame.click.trial = %%-input-trial-with-modifiers-%% ### option: Frame.click.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
## async method: Frame.content ## async method: Frame.content
@ -302,6 +303,7 @@ This method double clicks an element matching [`param: selector`] by performing
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set. Note that
if the first click of the `dblclick()` triggers a navigation event, this method will throw. if the first click of the `dblclick()` triggers a navigation event, this method will throw.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -326,7 +328,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.dblclick.modifiers = %%-input-modifiers-%% ### option: Frame.dblclick.modifiers = %%-input-modifiers-%%
* since: v1.8 * since: v1.8
### option: Frame.dblclick.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.dblclick.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.dblclick.position = %%-input-position-%% ### option: Frame.dblclick.position = %%-input-position-%%
@ -341,7 +343,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.dblclick.timeout = %%-input-timeout-js-%% ### option: Frame.dblclick.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Frame.dblclick.trial = %%-input-trial-with-modifiers-%% ### option: Frame.dblclick.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
## async method: Frame.dispatchEvent ## async method: Frame.dispatchEvent
@ -461,7 +463,7 @@ Optional event-specific initialization properties.
### option: Frame.dragAndDrop.force = %%-input-force-%% ### option: Frame.dragAndDrop.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: Frame.dragAndDrop.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.dragAndDrop.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.13 * since: v1.13
### option: Frame.dragAndDrop.strict = %%-input-strict-%% ### option: Frame.dragAndDrop.strict = %%-input-strict-%%
@ -854,7 +856,7 @@ Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
### option: Frame.fill.force = %%-input-force-%% ### option: Frame.fill.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: Frame.fill.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.fill.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.fill.strict = %%-input-strict-%% ### option: Frame.fill.strict = %%-input-strict-%%
@ -1128,6 +1130,7 @@ This method hovers over an element matching [`param: selector`] by performing th
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless `noWaitAfter` option is set.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
[TimeoutError]. Passing zero timeout disables this. [TimeoutError]. Passing zero timeout disables this.
@ -1153,10 +1156,10 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.hover.timeout = %%-input-timeout-js-%% ### option: Frame.hover.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Frame.hover.trial = %%-input-trial-with-modifiers-%% ### option: Frame.hover.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
### option: Frame.hover.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.hover.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28 * since: v1.28
## async method: Frame.innerHTML ## async method: Frame.innerHTML
@ -1304,7 +1307,7 @@ Returns whether the element is [enabled](../actionability.md#enabled).
* discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md). * discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md).
- returns: <[boolean]> - 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-%% ### param: Frame.isHidden.selector = %%-input-selector-%%
* since: v1.8 * since: v1.8
@ -1322,7 +1325,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). * discouraged: Use locator-based [`method: Locator.isVisible`] instead. Read more about [locators](../locators.md).
- returns: <[boolean]> - 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-%% ### param: Frame.isVisible.selector = %%-input-selector-%%
* since: v1.8 * since: v1.8
@ -1543,7 +1546,7 @@ await frame.SelectOptionAsync("select#colors", new[] { "red", "green", "blue" })
### option: Frame.selectOption.force = %%-input-force-%% ### option: Frame.selectOption.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: Frame.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.selectOption.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.selectOption.strict = %%-input-strict-%% ### option: Frame.selectOption.strict = %%-input-strict-%%
@ -1580,6 +1583,7 @@ This method checks or unchecks an element matching [`param: selector`] by perfor
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked or unchecked. If not, this method throws. 1. Ensure that the element is now checked or unchecked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -1594,7 +1598,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.setChecked.force = %%-input-force-%% ### option: Frame.setChecked.force = %%-input-force-%%
* since: v1.15 * since: v1.15
### option: Frame.setChecked.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.setChecked.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.15 * since: v1.15
### option: Frame.setChecked.position = %%-input-position-%% ### option: Frame.setChecked.position = %%-input-position-%%
@ -1648,7 +1652,7 @@ This method expects [`param: selector`] to point to an
### param: Frame.setInputFiles.files = %%-input-files-%% ### param: Frame.setInputFiles.files = %%-input-files-%%
* since: v1.8 * since: v1.8
### option: Frame.setInputFiles.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.setInputFiles.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.setInputFiles.strict = %%-input-strict-%% ### option: Frame.setInputFiles.strict = %%-input-strict-%%
@ -1671,6 +1675,7 @@ This method taps an element matching [`param: selector`] by performing the follo
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
[TimeoutError]. Passing zero timeout disables this. [TimeoutError]. Passing zero timeout disables this.
@ -1688,7 +1693,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.tap.modifiers = %%-input-modifiers-%% ### option: Frame.tap.modifiers = %%-input-modifiers-%%
* since: v1.8 * since: v1.8
### option: Frame.tap.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.tap.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.tap.position = %%-input-position-%% ### option: Frame.tap.position = %%-input-position-%%
@ -1703,7 +1708,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.tap.timeout = %%-input-timeout-js-%% ### option: Frame.tap.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Frame.tap.trial = %%-input-trial-with-modifiers-%% ### option: Frame.tap.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
## async method: Frame.textContent ## async method: Frame.textContent
@ -1757,7 +1762,7 @@ A text to type into a focused element.
Time to wait between key presses in milliseconds. Defaults to 0. Time to wait between key presses in milliseconds. Defaults to 0.
### option: Frame.type.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.type.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.type.strict = %%-input-strict-%% ### option: Frame.type.strict = %%-input-strict-%%
@ -1782,6 +1787,7 @@ This method checks an element matching [`param: selector`] by performing the fol
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now unchecked. If not, this method throws. 1. Ensure that the element is now unchecked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -1793,7 +1799,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Frame.uncheck.force = %%-input-force-%% ### option: Frame.uncheck.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: Frame.uncheck.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Frame.uncheck.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Frame.uncheck.position = %%-input-position-%% ### option: Frame.uncheck.position = %%-input-position-%%

View file

@ -1,30 +1,30 @@
# class: FrameLocator # class: FrameLocator
* since: v1.17 * 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 ```js
const locator = page.locator('#my-frame').contentFrame().getByText('Submit'); const locator = page.frameLocator('#my-frame').getByText('Submit');
await locator.click(); await locator.click();
``` ```
```java ```java
Locator locator = page.locator("#my-frame").contentFrame().getByText("Submit"); Locator locator = page.frameLocator("#my-frame").getByText("Submit");
locator.click(); locator.click();
``` ```
```python async ```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() await locator.click()
``` ```
```python sync ```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() locator.click()
``` ```
```csharp ```csharp
var locator = page.Locator("#my-frame").ContentFrame.GetByText("Submit"); var locator = page.FrameLocator("#my-frame").GetByText("Submit");
await locator.ClickAsync(); await locator.ClickAsync();
``` ```
@ -34,42 +34,42 @@ Frame locators are strict. This means that all operations on frame locators will
```js ```js
// Throws if there are several frames in DOM: // 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: // 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 ```python async
# Throws if there are several frames in DOM: # 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: # 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 ```python sync
# Throws if there are several frames in DOM: # 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: # 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 ```java
// Throws if there are several frames in DOM: // 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: // 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 ```csharp
// Throws if there are several frames in DOM: // 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: // 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** **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 ## method: FrameLocator.first
* deprecated: Use [`method: Locator.first`] followed by [`method: Locator.contentFrame`] instead.
* since: v1.17 * since: v1.17
- returns: <[FrameLocator]> - returns: <[FrameLocator]>
@ -172,7 +171,6 @@ in that iframe.
### option: FrameLocator.getByTitle.exact = %%-locator-get-by-text-exact-%% ### option: FrameLocator.getByTitle.exact = %%-locator-get-by-text-exact-%%
## method: FrameLocator.last ## method: FrameLocator.last
* deprecated: Use [`method: Locator.last`] followed by [`method: Locator.contentFrame`] instead.
* since: v1.17 * since: v1.17
- returns: <[FrameLocator]> - returns: <[FrameLocator]>
@ -197,7 +195,6 @@ Returns locator to the last matching frame.
* since: v1.33 * since: v1.33
## method: FrameLocator.nth ## method: FrameLocator.nth
* deprecated: Use [`method: Locator.nth`] followed by [`method: Locator.contentFrame`] instead.
* since: v1.17 * since: v1.17
- returns: <[FrameLocator]> - returns: <[FrameLocator]>
@ -220,36 +217,37 @@ For a reverse operation, use [`method: Locator.contentFrame`].
**Usage** **Usage**
```js ```js
const frameLocator = page.locator('iframe[name="embedded"]').contentFrame(); const frameLocator = page.frameLocator('iframe[name="embedded"]');
// ... // ...
const locator = frameLocator.owner(); const locator = frameLocator.owner();
await expect(locator).toBeVisible(); await expect(locator).toBeVisible();
``` ```
```java ```java
FrameLocator frameLocator = page.locator("iframe[name=\"embedded\"]").contentFrame(); FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]");
// ... // ...
Locator locator = frameLocator.owner(); Locator locator = frameLocator.owner();
assertThat(locator).isVisible(); assertThat(locator).isVisible();
``` ```
```python async ```python async
frame_locator = page.locator("iframe[name=\"embedded\"]").content_frame frame_locator = page.frame_locator("iframe[name=\"embedded\"]")
# ... # ...
locator = frame_locator.owner locator = frame_locator.owner
await expect(locator).to_be_visible() await expect(locator).to_be_visible()
``` ```
```python sync ```python sync
frame_locator = page.locator("iframe[name=\"embedded\"]").content_frame frame_locator = page.frame_locator("iframe[name=\"embedded\"]")
# ... # ...
locator = frame_locator.owner locator = frame_locator.owner
expect(locator).to_be_visible() expect(locator).to_be_visible()
``` ```
```csharp ```csharp
var frameLocator = Page.Locator("iframe[name=\"embedded\"]").ContentFrame; var frameLocator = Page.FrameLocator("iframe[name=\"embedded\"]");
// ... // ...
var locator = frameLocator.Owner; var locator = frameLocator.Owner;
await Expect(locator).ToBeVisibleAsync(); await Expect(locator).ToBeVisibleAsync();
``` ```

View file

@ -104,23 +104,38 @@ await page.Keyboard.PressAsync("Shift+A");
An example to trigger select-all with the keyboard An example to trigger select-all with the keyboard
```js ```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 ```java
page.keyboard().press("ControlOrMeta+A"); // on Windows and Linux
page.keyboard().press("Control+A");
// on macOS
page.keyboard().press("Meta+A");
``` ```
```python async ```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 ```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 ```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 ## async method: Keyboard.down
@ -242,7 +257,7 @@ await browser.close();
Page page = browser.newPage(); Page page = browser.newPage();
page.navigate("https://keycode.info"); page.navigate("https://keycode.info");
page.keyboard().press("A"); 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.keyboard().press("ArrowLeft");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png"))); page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
page.keyboard().press("Shift+O"); page.keyboard().press("Shift+O");

View file

@ -38,7 +38,7 @@ for li in page.get_by_role('listitem').all():
``` ```
```java ```java
for (Locator li : page.getByRole("listitem").all()) for (Locator li : page.getByRole('listitem').all())
li.click(); 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. Returns an array of `node.innerText` values for all matching nodes.
:::warning[Asserting text] :::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** **Usage**
@ -150,67 +150,6 @@ var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
Additional locator to match. 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 ## async method: Locator.blur
* since: v1.28 * since: v1.28
@ -292,6 +231,7 @@ Performs the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked. If not, this method throws. 1. Ensure that the element is now checked. If not, this method throws.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -327,7 +267,7 @@ await page.GetByRole(AriaRole.Checkbox).CheckAsync();
### option: Locator.check.force = %%-input-force-%% ### option: Locator.check.force = %%-input-force-%%
* since: v1.14 * since: v1.14
### option: Locator.check.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.check.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.check.timeout = %%-input-timeout-%% ### option: Locator.check.timeout = %%-input-timeout-%%
@ -377,7 +317,7 @@ await page.GetByRole(AriaRole.Textbox).ClearAsync();
### option: Locator.clear.force = %%-input-force-%% ### option: Locator.clear.force = %%-input-force-%%
* since: v1.28 * since: v1.28
### option: Locator.clear.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.clear.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28 * since: v1.28
### option: Locator.clear.timeout = %%-input-timeout-%% ### option: Locator.clear.timeout = %%-input-timeout-%%
@ -494,7 +434,7 @@ await page.Locator("canvas").ClickAsync(new() {
### option: Locator.click.timeout = %%-input-timeout-js-%% ### option: Locator.click.timeout = %%-input-timeout-js-%%
* since: v1.14 * since: v1.14
### option: Locator.click.trial = %%-input-trial-with-modifiers-%% ### option: Locator.click.trial = %%-input-trial-%%
* since: v1.14 * since: v1.14
## async method: Locator.count ## async method: Locator.count
@ -543,6 +483,8 @@ This method double clicks the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set. Note that
if the first click of the `dblclick()` triggers a navigation event, this method will throw.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -568,7 +510,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.dblclick.force = %%-input-force-%% ### option: Locator.dblclick.force = %%-input-force-%%
* since: v1.14 * since: v1.14
### option: Locator.dblclick.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.dblclick.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.dblclick.timeout = %%-input-timeout-%% ### option: Locator.dblclick.timeout = %%-input-timeout-%%
@ -577,7 +519,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.dblclick.timeout = %%-input-timeout-js-%% ### option: Locator.dblclick.timeout = %%-input-timeout-js-%%
* since: v1.14 * since: v1.14
### option: Locator.dblclick.trial = %%-input-trial-with-modifiers-%% ### option: Locator.dblclick.trial = %%-input-trial-%%
* since: v1.14 * since: v1.14
## async method: Locator.dispatchEvent ## async method: Locator.dispatchEvent
@ -633,11 +575,13 @@ properties:
You can also specify [JSHandle] as the property value if you want live objects to be passed into the event: You can also specify [JSHandle] as the property value if you want live objects to be passed into the event:
```js ```js
// Note you can only create DataTransfer in Chromium and Firefox
const dataTransfer = await page.evaluateHandle(() => new DataTransfer()); const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await locator.dispatchEvent('dragstart', { dataTransfer }); await locator.dispatchEvent('dragstart', { dataTransfer });
``` ```
```java ```java
// Note you can only create DataTransfer in Chromium and Firefox
JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()"); JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
Map<String, Object> arg = new HashMap<>(); Map<String, Object> arg = new HashMap<>();
arg.put("dataTransfer", dataTransfer); arg.put("dataTransfer", dataTransfer);
@ -645,11 +589,13 @@ locator.dispatchEvent("dragstart", arg);
``` ```
```python async ```python async
# note you can only create data_transfer in chromium and firefox
data_transfer = await page.evaluate_handle("new DataTransfer()") data_transfer = await page.evaluate_handle("new DataTransfer()")
await locator.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer}) await locator.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer})
``` ```
```python sync ```python sync
# note you can only create data_transfer in chromium and firefox
data_transfer = page.evaluate_handle("new DataTransfer()") data_transfer = page.evaluate_handle("new DataTransfer()")
locator.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer}) locator.dispatch_event("#source", "dragstart", {"dataTransfer": data_transfer})
``` ```
@ -763,7 +709,7 @@ Locator of the element to drag to.
### option: Locator.dragTo.force = %%-input-force-%% ### option: Locator.dragTo.force = %%-input-force-%%
* since: v1.18 * since: v1.18
### option: Locator.dragTo.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.dragTo.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.18 * since: v1.18
### option: Locator.dragTo.timeout = %%-input-timeout-%% ### option: Locator.dragTo.timeout = %%-input-timeout-%%
@ -864,6 +810,31 @@ If [`param: expression`] throws or rejects, this method throws.
**Usage** **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-%% ### param: Locator.evaluate.expression = %%-evaluate-expression-%%
* since: v1.14 * since: v1.14
@ -1015,7 +986,7 @@ Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
### option: Locator.fill.force = %%-input-force-%% ### option: Locator.fill.force = %%-input-force-%%
* since: v1.14 * since: v1.14
### option: Locator.fill.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.fill.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.fill.timeout = %%-input-timeout-%% ### option: Locator.fill.timeout = %%-input-timeout-%%
@ -1090,9 +1061,6 @@ await rowLocator
### option: Locator.filter.hasNotText = %%-locator-option-has-not-text-%% ### option: Locator.filter.hasNotText = %%-locator-option-has-not-text-%%
* since: v1.33 * since: v1.33
### option: Locator.filter.visible = %%-locator-option-visible-%%
* since: v1.51
## method: Locator.first ## method: Locator.first
* since: v1.14 * since: v1.14
- returns: <[Locator]> - returns: <[Locator]>
@ -1280,6 +1248,7 @@ This method hovers over the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless `noWaitAfter` option is set.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -1301,10 +1270,10 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.hover.timeout = %%-input-timeout-js-%% ### option: Locator.hover.timeout = %%-input-timeout-js-%%
* since: v1.14 * since: v1.14
### option: Locator.hover.trial = %%-input-trial-with-modifiers-%% ### option: Locator.hover.trial = %%-input-trial-%%
* since: v1.14 * since: v1.14
### option: Locator.hover.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.hover.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28 * since: v1.28
## async method: Locator.innerHTML ## async method: Locator.innerHTML
@ -1326,7 +1295,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). Returns the [`element.innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText).
:::warning[Asserting text] :::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-%% ### option: Locator.innerText.timeout = %%-input-timeout-%%
@ -1457,7 +1426,7 @@ Boolean disabled = await page.GetByRole(AriaRole.Button).IsDisabledAsync();
* since: v1.14 * since: v1.14
- returns: <[boolean]> - 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] :::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. 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 +1658,16 @@ var banana = await page.GetByRole(AriaRole.Listitem).Nth(2);
- alias-python: or_ - alias-python: or_
- returns: <[Locator]> - returns: <[Locator]>
Creates a locator matching all elements that match one or both of the two locators. Creates a locator that matches either 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.
**Usage** **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. 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 ```js
const newEmail = page.getByRole('button', { name: 'New' }); const newEmail = page.getByRole('button', { name: 'New' });
const dialog = page.getByText('Confirm security settings'); const dialog = page.getByText('Confirm security settings');
await expect(newEmail.or(dialog).first()).toBeVisible(); await expect(newEmail.or(dialog)).toBeVisible();
if (await dialog.isVisible()) if (await dialog.isVisible())
await page.getByRole('button', { name: 'Dismiss' }).click(); await page.getByRole('button', { name: 'Dismiss' }).click();
await newEmail.click(); await newEmail.click();
@ -1714,7 +1676,7 @@ await newEmail.click();
```java ```java
Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New")); Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
Locator dialog = page.getByText("Confirm security settings"); Locator dialog = page.getByText("Confirm security settings");
assertThat(newEmail.or(dialog).first()).isVisible(); assertThat(newEmail.or(dialog)).isVisible();
if (dialog.isVisible()) if (dialog.isVisible())
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click(); page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
newEmail.click(); newEmail.click();
@ -1723,7 +1685,7 @@ newEmail.click();
```python async ```python async
new_email = page.get_by_role("button", name="New") new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings") 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()): if (await dialog.is_visible()):
await page.get_by_role("button", name="Dismiss").click() await page.get_by_role("button", name="Dismiss").click()
await new_email.click() await new_email.click()
@ -1732,7 +1694,7 @@ await new_email.click()
```python sync ```python sync
new_email = page.get_by_role("button", name="New") new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings") 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()): if (dialog.is_visible()):
page.get_by_role("button", name="Dismiss").click() page.get_by_role("button", name="Dismiss").click()
new_email.click() new_email.click()
@ -1741,7 +1703,7 @@ new_email.click()
```csharp ```csharp
var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" }); var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" });
var dialog = page.GetByText("Confirm security settings"); var dialog = page.GetByText("Confirm security settings");
await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync(); await Expect(newEmail.Or(dialog)).ToBeVisibleAsync();
if (await dialog.IsVisibleAsync()) if (await dialog.IsVisibleAsync())
await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync(); await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
await newEmail.ClickAsync(); await newEmail.ClickAsync();
@ -1914,7 +1876,7 @@ String of characters to sequentially press into a focused element.
Time to wait between key presses in milliseconds. Defaults to 0. Time to wait between key presses in milliseconds. Defaults to 0.
### option: Locator.pressSequentially.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.pressSequentially.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.38 * since: v1.38
### option: Locator.pressSequentially.timeout = %%-input-timeout-%% ### option: Locator.pressSequentially.timeout = %%-input-timeout-%%
@ -2010,8 +1972,6 @@ This method waits for [actionability](../actionability.md) checks, then tries to
completely visible as defined by completely visible as defined by
[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)'s `ratio`. [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)'s `ratio`.
See [scrolling](../input.md#scrolling) for alternative ways to scroll.
### option: Locator.scrollIntoViewIfNeeded.timeout = %%-input-timeout-%% ### option: Locator.scrollIntoViewIfNeeded.timeout = %%-input-timeout-%%
* since: v1.14 * since: v1.14
@ -2038,9 +1998,9 @@ Triggers a `change` and `input` event once all the provided options have been se
```html ```html
<select multiple> <select multiple>
<option value="red">Red</option> <option value="red">Red</div>
<option value="green">Green</option> <option value="green">Green</div>
<option value="blue">Blue</option> <option value="blue">Blue</div>
</select> </select>
``` ```
@ -2097,7 +2057,7 @@ await element.SelectOptionAsync(new[] { "red", "green", "blue" });
### option: Locator.selectOption.force = %%-input-force-%% ### option: Locator.selectOption.force = %%-input-force-%%
* since: v1.14 * since: v1.14
### option: Locator.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.selectOption.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.selectOption.timeout = %%-input-timeout-%% ### option: Locator.selectOption.timeout = %%-input-timeout-%%
@ -2171,6 +2131,7 @@ This method checks or unchecks an element by performing the following steps:
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked or unchecked. If not, this method throws. 1. Ensure that the element is now checked or unchecked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -2182,7 +2143,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.setChecked.force = %%-input-force-%% ### option: Locator.setChecked.force = %%-input-force-%%
* since: v1.15 * since: v1.15
### option: Locator.setChecked.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.setChecked.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.15 * since: v1.15
### option: Locator.setChecked.position = %%-input-position-%% ### option: Locator.setChecked.position = %%-input-position-%%
@ -2201,7 +2162,6 @@ When all steps combined have not finished during the specified [`option: timeout
* since: v1.14 * since: v1.14
Upload file or multiple files into `<input type=file>`. Upload file or multiple files into `<input type=file>`.
For inputs with a `[webkitdirectory]` attribute, only a single directory path is supported.
**Usage** **Usage**
@ -2215,9 +2175,6 @@ await page.getByLabel('Upload files').setInputFiles([
path.join(__dirname, 'file2.txt'), path.join(__dirname, 'file2.txt'),
]); ]);
// Select a directory
await page.getByLabel('Upload directory').setInputFiles(path.join(__dirname, 'mydir'));
// Remove all the selected files // Remove all the selected files
await page.getByLabel('Upload file').setInputFiles([]); await page.getByLabel('Upload file').setInputFiles([]);
@ -2236,9 +2193,6 @@ page.getByLabel("Upload file").setInputFiles(Paths.get("myfile.pdf"));
// Select multiple files // Select multiple files
page.getByLabel("Upload files").setInputFiles(new Path[] {Paths.get("file1.txt"), Paths.get("file2.txt")}); page.getByLabel("Upload files").setInputFiles(new Path[] {Paths.get("file1.txt"), Paths.get("file2.txt")});
// Select a directory
page.getByLabel("Upload directory").setInputFiles(Paths.get("mydir"));
// Remove all the selected files // Remove all the selected files
page.getByLabel("Upload file").setInputFiles(new Path[0]); page.getByLabel("Upload file").setInputFiles(new Path[0]);
@ -2254,9 +2208,6 @@ await page.get_by_label("Upload file").set_input_files('myfile.pdf')
# Select multiple files # Select multiple files
await page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt']) await page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Select a directory
await page.get_by_label("Upload directory").set_input_files('mydir')
# Remove all the selected files # Remove all the selected files
await page.get_by_label("Upload file").set_input_files([]) await page.get_by_label("Upload file").set_input_files([])
@ -2275,9 +2226,6 @@ page.get_by_label("Upload file").set_input_files('myfile.pdf')
# Select multiple files # Select multiple files
page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt']) page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])
# Select a directory
page.get_by_label("Upload directory").set_input_files('mydir')
# Remove all the selected files # Remove all the selected files
page.get_by_label("Upload file").set_input_files([]) page.get_by_label("Upload file").set_input_files([])
@ -2296,9 +2244,6 @@ await page.GetByLabel("Upload file").SetInputFilesAsync("myfile.pdf");
// Select multiple files // Select multiple files
await page.GetByLabel("Upload files").SetInputFilesAsync(new[] { "file1.txt", "file12.txt" }); await page.GetByLabel("Upload files").SetInputFilesAsync(new[] { "file1.txt", "file12.txt" });
// Select a directory
await page.GetByLabel("Upload directory").SetInputFilesAsync("mydir");
// Remove all the selected files // Remove all the selected files
await page.GetByLabel("Upload file").SetInputFilesAsync(new[] {}); await page.GetByLabel("Upload file").SetInputFilesAsync(new[] {});
@ -2323,7 +2268,7 @@ This method expects [Locator] to point to an
### param: Locator.setInputFiles.files = %%-input-files-%% ### param: Locator.setInputFiles.files = %%-input-files-%%
* since: v1.14 * since: v1.14
### option: Locator.setInputFiles.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.setInputFiles.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.setInputFiles.timeout = %%-input-timeout-%% ### option: Locator.setInputFiles.timeout = %%-input-timeout-%%
@ -2335,7 +2280,7 @@ This method expects [Locator] to point to an
## async method: Locator.tap ## async method: Locator.tap
* since: v1.14 * 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** **Details**
@ -2343,6 +2288,7 @@ This method taps the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -2362,7 +2308,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.tap.force = %%-input-force-%% ### option: Locator.tap.force = %%-input-force-%%
* since: v1.14 * since: v1.14
### option: Locator.tap.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.tap.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.tap.timeout = %%-input-timeout-%% ### option: Locator.tap.timeout = %%-input-timeout-%%
@ -2371,7 +2317,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.tap.timeout = %%-input-timeout-js-%% ### option: Locator.tap.timeout = %%-input-timeout-js-%%
* since: v1.14 * since: v1.14
### option: Locator.tap.trial = %%-input-trial-with-modifiers-%% ### option: Locator.tap.trial = %%-input-trial-%%
* since: v1.14 * since: v1.14
## async method: Locator.textContent ## async method: Locator.textContent
@ -2412,7 +2358,7 @@ A text to type into a focused element.
Time to wait between key presses in milliseconds. Defaults to 0. Time to wait between key presses in milliseconds. Defaults to 0.
### option: Locator.type.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.type.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.type.timeout = %%-input-timeout-%% ### option: Locator.type.timeout = %%-input-timeout-%%
@ -2456,6 +2402,7 @@ This method unchecks the element by performing the following steps:
1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set. 1. Wait for [actionability](../actionability.md) checks on the element, unless [`option: force`] option is set.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now unchecked. If not, this method throws. 1. Ensure that the element is now unchecked. If not, this method throws.
If the element is detached from the DOM at any moment during the action, this method throws. If the element is detached from the DOM at any moment during the action, this method throws.
@ -2469,7 +2416,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Locator.uncheck.force = %%-input-force-%% ### option: Locator.uncheck.force = %%-input-force-%%
* since: v1.14 * since: v1.14
### option: Locator.uncheck.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Locator.uncheck.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.14 * since: v1.14
### option: Locator.uncheck.timeout = %%-input-timeout-%% ### option: Locator.uncheck.timeout = %%-input-timeout-%%

View file

@ -14,14 +14,14 @@ test('status becomes submitted', async ({ page }) => {
``` ```
```java ```java
// ... ...
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestLocator { public class TestLocator {
// ... ...
@Test @Test
void statusBecomesSubmitted() { void statusBecomesSubmitted() {
// ... ...
page.getByRole(AriaRole.BUTTON).click(); page.getByRole(AriaRole.BUTTON).click();
assertThat(page.locator(".status")).hasText("Submitted"); assertThat(page.locator(".status")).hasText("Submitted");
} }
@ -47,19 +47,21 @@ def test_status_becomes_submitted(page: Page) -> None:
``` ```
```csharp ```csharp
using Microsoft.Playwright; using System.Text.RegularExpressions;
using Microsoft.Playwright.MSTest; using System.Threading.Tasks;
using Microsoft.Playwright.NUnit;
using NUnit.Framework;
namespace PlaywrightTests; namespace PlaywrightTests;
[TestClass] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[TestMethod] [Test]
public async Task StatusBecomesSubmitted() public async Task StatusBecomesSubmitted()
{ {
// ... // ..
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync(); await Page.GetByRole(AriaRole.Button).ClickAsync();
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted"); await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");
} }
} }
@ -240,24 +242,6 @@ Expected accessible description.
### option: LocatorAssertions.NotToHaveAccessibleDescription.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.NotToHaveAccessibleDescription.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.44 * 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 ## async method: LocatorAssertions.NotToHaveAccessibleName
* since: v1.44 * since: v1.44
@ -460,23 +444,6 @@ Expected options currently selected.
### option: LocatorAssertions.NotToHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.NotToHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.23 * 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 ## async method: LocatorAssertions.toBeAttached
* since: v1.33 * since: v1.33
@ -559,16 +526,6 @@ await Expect(locator).ToBeCheckedAsync();
* since: v1.18 * since: v1.18
- `checked` <[boolean]> - `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-%% ### option: LocatorAssertions.toBeChecked.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18
@ -746,7 +703,7 @@ expect(locator).to_be_enabled()
```csharp ```csharp
var locator = Page.Locator("button.submit"); var locator = Page.Locator("button.submit");
await Expect(locator).ToBeEnabledAsync(); await Expect(locator).toBeEnabledAsync();
``` ```
### option: LocatorAssertions.toBeEnabled.enabled ### option: LocatorAssertions.toBeEnabled.enabled
@ -953,10 +910,10 @@ await expect(
assertThat(page.getByText("Welcome")).isVisible(); assertThat(page.getByText("Welcome")).isVisible();
// At least one item in the list is visible. // At least one item in the list is visible.
assertThat(page.getByTestId("todo-item").first()).isVisible(); asserThat(page.getByTestId("todo-item").first()).isVisible();
// At least one of the two elements is visible, possibly both. // At least one of the two elements is visible, possibly both.
assertThat( asserThat(
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in")) page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up"))) .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
.first() .first()
@ -1226,7 +1183,7 @@ expect(locator).to_have_accessible_description("Save results to disk")
```csharp ```csharp
var locator = Page.GetByTestId("save-button"); 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 ### param: LocatorAssertions.toHaveAccessibleDescription.description
@ -1245,56 +1202,6 @@ Expected accessible description.
* since: v1.44 * 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 ## async method: LocatorAssertions.toHaveAccessibleName
* since: v1.44 * since: v1.44
* langs: * langs:
@ -1326,7 +1233,7 @@ expect(locator).to_have_accessible_name("Save to disk")
```csharp ```csharp
var locator = Page.GetByTestId("save-button"); var locator = Page.GetByTestId("save-button");
await Expect(locator).ToHaveAccessibleNameAsync("Save to disk"); await Expect(locator).toHaveAccessibleNameAsync("Save to disk");
``` ```
### param: LocatorAssertions.toHaveAccessibleName.name ### param: LocatorAssertions.toHaveAccessibleName.name
@ -1431,48 +1338,49 @@ Attribute name.
* langs: * langs:
- alias-java: hasClass - 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** **Usage**
```html ```html
<div class='middle selected row' id='component'></div> <div class='selected row' id='component'></div>
``` ```
```js ```js
const locator = page.locator('#component'); const locator = page.locator('#component');
await expect(locator).toHaveClass('middle selected row'); await expect(locator).toHaveClass(/selected/);
await expect(locator).toHaveClass(/(^|\s)selected(\s|$)/); await expect(locator).toHaveClass('selected row');
``` ```
```java ```java
assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)")); assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
assertThat(page.locator("#component")).hasClass("middle selected row"); assertThat(page.locator("#component")).hasClass("selected row");
``` ```
```python async ```python async
from playwright.async_api import expect from playwright.async_api import expect
locator = page.locator("#component") locator = page.locator("#component")
await expect(locator).to_have_class(re.compile(r"(^|\\s)selected(\\s|$)")) await expect(locator).to_have_class(re.compile(r"selected"))
await expect(locator).to_have_class("middle selected row") await expect(locator).to_have_class("selected row")
``` ```
```python sync ```python sync
from playwright.sync_api import expect from playwright.sync_api import expect
locator = page.locator("#component") locator = page.locator("#component")
expect(locator).to_have_class(re.compile(r"(^|\\s)selected(\\s|$)")) expect(locator).to_have_class(re.compile(r"selected"))
expect(locator).to_have_class("middle selected row") expect(locator).to_have_class("selected row")
``` ```
```csharp ```csharp
var locator = Page.Locator("#component"); var locator = Page.Locator("#component");
await Expect(locator).ToHaveClassAsync(new Regex("(^|\\s)selected(\\s|$)")); await Expect(locator).ToHaveClassAsync(new Regex("selected"));
await Expect(locator).ToHaveClassAsync("middle selected row"); 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 ```js
const locator = page.locator('list > .component'); const locator = page.locator('list > .component');
@ -2142,7 +2050,7 @@ await expect(locator).toHaveValues([/R/, /G/]);
``` ```
```java ```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") }); assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
``` ```
@ -2197,91 +2105,3 @@ Expected options currently selected.
### option: LocatorAssertions.toHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.toHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.23 * 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

View file

@ -68,14 +68,10 @@ Shortcut for [`method: Mouse.move`], [`method: Mouse.down`], [`method: Mouse.up`
* since: v1.8 * since: v1.8
- `x` <[float]> - `x` <[float]>
X coordinate relative to the main frame's viewport in CSS pixels.
### param: Mouse.click.y ### param: Mouse.click.y
* since: v1.8 * since: v1.8
- `y` <[float]> - `y` <[float]>
Y coordinate relative to the main frame's viewport in CSS pixels.
### option: Mouse.click.button = %%-input-button-%% ### option: Mouse.click.button = %%-input-button-%%
* since: v1.8 * since: v1.8
@ -97,14 +93,10 @@ Shortcut for [`method: Mouse.move`], [`method: Mouse.down`], [`method: Mouse.up`
* since: v1.8 * since: v1.8
- `x` <[float]> - `x` <[float]>
X coordinate relative to the main frame's viewport in CSS pixels.
### param: Mouse.dblclick.y ### param: Mouse.dblclick.y
* since: v1.8 * since: v1.8
- `y` <[float]> - `y` <[float]>
Y coordinate relative to the main frame's viewport in CSS pixels.
### option: Mouse.dblclick.button = %%-input-button-%% ### option: Mouse.dblclick.button = %%-input-button-%%
* since: v1.8 * since: v1.8
@ -131,14 +123,10 @@ Dispatches a `mousemove` event.
* since: v1.8 * since: v1.8
- `x` <[float]> - `x` <[float]>
X coordinate relative to the main frame's viewport in CSS pixels.
### param: Mouse.move.y ### param: Mouse.move.y
* since: v1.8 * since: v1.8
- `y` <[float]> - `y` <[float]>
Y coordinate relative to the main frame's viewport in CSS pixels.
### option: Mouse.move.steps ### option: Mouse.move.steps
* since: v1.8 * since: v1.8
- `steps` <[int]> - `steps` <[int]>
@ -159,7 +147,7 @@ Dispatches a `mouseup` event.
## async method: Mouse.wheel ## async method: Mouse.wheel
* since: v1.15 * since: v1.15
Dispatches a `wheel` event. This method is usually used to manually scroll the page. See [scrolling](../input.md#scrolling) for alternative ways to scroll. Dispatches a `wheel` event.
:::note :::note
Wheel events may cause scrolling if they are not handled, and this method does not Wheel events may cause scrolling if they are not handled, and this method does not

View file

@ -1,5 +1,6 @@
# class: Page # class: Page
* since: v1.8 * since: v1.8
* extends: [EventEmitter]
Page provides methods to interact with a single tab in a [Browser], or an Page provides methods to interact with a single tab in a [Browser], or an
[extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One [Browser] [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One [Browser]
@ -150,12 +151,6 @@ page.Load += PageLoadHandler;
page.Load -= PageLoadHandler; page.Load -= PageLoadHandler;
``` ```
## property: Page.clock
* since: v1.45
- type: <[Clock]>
Playwright has ability to mock clock and passage of time.
## event: Page.close ## event: Page.close
* since: v1.8 * since: v1.8
- argument: <[Page]> - argument: <[Page]>
@ -687,7 +682,7 @@ Raw JavaScript content to be injected into frame.
* since: v1.8 * since: v1.8
- `type` <[string]> - `type` <[string]>
Script type. Use 'module' in order to load a JavaScript ES6 module. See Script type. Use 'module' in order to load a Javascript ES6 module. See
[script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) for more details. [script](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) for more details.
## async method: Page.addStyleTag ## async method: Page.addStyleTag
@ -734,6 +729,7 @@ This method checks an element matching [`param: selector`] by performing the fol
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked. If not, this method throws. 1. Ensure that the element is now checked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -745,7 +741,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.check.force = %%-input-force-%% ### option: Page.check.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: Page.check.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.check.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.check.position = %%-input-position-%% ### option: Page.check.position = %%-input-position-%%
@ -812,7 +808,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.click.timeout = %%-input-timeout-js-%% ### option: Page.click.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Page.click.trial = %%-input-trial-with-modifiers-%% ### option: Page.click.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
## async method: Page.close ## async method: Page.close
@ -877,6 +873,8 @@ This method double clicks an element matching [`param: selector`] by performing
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to double click in the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set. Note that
if the first click of the `dblclick()` triggers a navigation event, this method will throw.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
[TimeoutError]. Passing zero timeout disables this. [TimeoutError]. Passing zero timeout disables this.
@ -900,7 +898,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.dblclick.modifiers = %%-input-modifiers-%% ### option: Page.dblclick.modifiers = %%-input-modifiers-%%
* since: v1.8 * since: v1.8
### option: Page.dblclick.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.dblclick.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.dblclick.position = %%-input-position-%% ### option: Page.dblclick.position = %%-input-position-%%
@ -915,7 +913,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.dblclick.timeout = %%-input-timeout-js-%% ### option: Page.dblclick.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Page.dblclick.trial = %%-input-trial-with-modifiers-%% ### option: Page.dblclick.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
## async method: Page.dispatchEvent ## async method: Page.dispatchEvent
@ -1041,9 +1039,9 @@ await page.dragAndDrop('#source', '#target', {
``` ```
```java ```java
page.dragAndDrop("#source", "#target"); page.dragAndDrop("#source", '#target');
// or specify exact positions relative to the top-left corners of the elements: // 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)); .setSourcePosition(34, 7).setTargetPosition(10, 20));
``` ```
@ -1088,7 +1086,7 @@ await Page.DragAndDropAsync("#source", "#target", new()
### option: Page.dragAndDrop.force = %%-input-force-%% ### option: Page.dragAndDrop.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: Page.dragAndDrop.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.dragAndDrop.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.13 * since: v1.13
### option: Page.dragAndDrop.strict = %%-input-strict-%% ### option: Page.dragAndDrop.strict = %%-input-strict-%%
@ -1217,6 +1215,8 @@ await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches);
// → true // → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches); await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches);
// → false // → false
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches);
// → false
``` ```
```java ```java
@ -1225,6 +1225,8 @@ page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches");
// → true // → true
page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"); page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches");
// → false // → false
page.evaluate("() => matchMedia('(prefers-color-scheme: no-preference)').matches");
// → false
``` ```
```python async ```python async
@ -1233,6 +1235,8 @@ await page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches")
# → True # → True
await page.evaluate("matchMedia('(prefers-color-scheme: light)').matches") await page.evaluate("matchMedia('(prefers-color-scheme: light)').matches")
# → False # → False
await page.evaluate("matchMedia('(prefers-color-scheme: no-preference)').matches")
# → False
``` ```
```python sync ```python sync
@ -1241,6 +1245,7 @@ page.evaluate("matchMedia('(prefers-color-scheme: dark)').matches")
# → True # → True
page.evaluate("matchMedia('(prefers-color-scheme: light)').matches") page.evaluate("matchMedia('(prefers-color-scheme: light)').matches")
# → False # → False
page.evaluate("matchMedia('(prefers-color-scheme: no-preference)').matches")
``` ```
```csharp ```csharp
@ -1249,6 +1254,8 @@ await page.EvaluateAsync("matchMedia('(prefers-color-scheme: dark)').matches");
// → true // → true
await page.EvaluateAsync("matchMedia('(prefers-color-scheme: light)').matches"); await page.EvaluateAsync("matchMedia('(prefers-color-scheme: light)').matches");
// → false // → false
await page.EvaluateAsync("matchMedia('(prefers-color-scheme: no-preference)').matches");
// → false
``` ```
### option: Page.emulateMedia.media ### option: Page.emulateMedia.media
@ -1272,16 +1279,16 @@ Passing `'Null'` disables CSS media emulation.
* langs: js, java * langs: js, java
- `colorScheme` <null|[ColorScheme]<"light"|"dark"|"no-preference">> - `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 Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing
`null` disables color scheme emulation. `'no-preference'` is deprecated. `null` disables color scheme emulation.
### option: Page.emulateMedia.colorScheme ### option: Page.emulateMedia.colorScheme
* since: v1.9 * since: v1.9
* langs: csharp, python * langs: csharp, python
- `colorScheme` <[ColorScheme]<"light"|"dark"|"no-preference"|"null">> - `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 Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing
`'Null'` disables color scheme emulation. `'no-preference'` is deprecated. `'Null'` disables color scheme emulation.
### option: Page.emulateMedia.reducedMotion ### option: Page.emulateMedia.reducedMotion
* since: v1.12 * since: v1.12
@ -1309,18 +1316,6 @@ Emulates `'forced-colors'` media feature, supported values are `'active'` and `'
* langs: csharp, python * langs: csharp, python
- `forcedColors` <[ForcedColors]<"active"|"none"|"null">> - `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 ## async method: Page.evalOnSelector
* since: v1.9 * since: v1.9
* discouraged: This method does not wait for the element to pass actionability * discouraged: This method does not wait for the element to pass actionability
@ -1719,7 +1714,7 @@ public class Example {
public static void main(String[] args) { public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) { try (Playwright playwright = Playwright.create()) {
BrowserType webkit = playwright.webkit(); BrowserType webkit = playwright.webkit();
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); Browser browser = webkit.launch({ headless: false });
BrowserContext context = browser.newContext(); BrowserContext context = browser.newContext();
Page page = context.newPage(); Page page = context.newPage();
page.exposeBinding("pageURL", (source, args) -> source.page().url()); page.exposeBinding("pageURL", (source, args) -> source.page().url());
@ -1816,6 +1811,80 @@ class PageExamples
} }
``` ```
An example of passing an element handle:
```js
await page.exposeBinding('clicked', async (source, element) => {
console.log(await element.textContent());
}, { handle: true });
await page.setContent(`
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
`);
```
```java
page.exposeBinding("clicked", (source, args) -> {
ElementHandle element = (ElementHandle) args[0];
System.out.println(element.textContent());
return null;
}, new Page.ExposeBindingOptions().setHandle(true));
page.setContent("" +
"<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
```
```python async
async def print(source, element):
print(await element.text_content())
await page.expose_binding("clicked", print, handle=true)
await page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```python sync
def print(source, element):
print(element.text_content())
page.expose_binding("clicked", print, handle=true)
page.set_content("""
<script>
document.addEventListener('click', event => window.clicked(event.target));
</script>
<div>Click me</div>
<div>Or click me</div>
""")
```
```csharp
var result = new TaskCompletionSource<string>();
await page.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
{
return result.TrySetResult(await t.AsElement().TextContentAsync());
});
await page.SetContentAsync("<script>\n" +
" document.addEventListener('click', event => window.clicked(event.target));\n" +
"</script>\n" +
"<div>Click me</div>\n" +
"<div>Or click me</div>\n");
await page.ClickAsync("div");
Console.WriteLine(await result.Task);
```
### param: Page.exposeBinding.name ### param: Page.exposeBinding.name
* since: v1.8 * since: v1.8
- `name` <[string]> - `name` <[string]>
@ -1830,7 +1899,6 @@ Callback function that will be called in the Playwright's context.
### option: Page.exposeBinding.handle ### option: Page.exposeBinding.handle
* since: v1.8 * since: v1.8
* deprecated: This option will be removed in the future.
- `handle` <[boolean]> - `handle` <[boolean]>
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@ -1889,27 +1957,26 @@ public class Example {
public static void main(String[] args) { public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) { try (Playwright playwright = Playwright.create()) {
BrowserType webkit = playwright.webkit(); BrowserType webkit = playwright.webkit();
Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); Browser browser = webkit.launch({ headless: false });
Page page = browser.newPage(); Page page = browser.newPage();
page.exposeFunction("sha256", args -> { page.exposeFunction("sha256", args -> {
try {
String text = (String) args[0]; String text = (String) args[0];
MessageDigest crypto = MessageDigest.getInstance("SHA-256"); MessageDigest crypto;
byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8)); try {
return Base64.getEncoder().encodeToString(token); crypto = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
return null; return null;
} }
byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(token);
}); });
page.setContent( page.setContent("<script>\n" +
"<script>\n" +
" async function onClick() {\n" + " async function onClick() {\n" +
" document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');\n" + " document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');\n" +
" }\n" + " }\n" +
"</script>\n" + "</script>\n" +
"<button onclick=\"onClick()\">Click me</button>\n" + "<button onclick=\"onClick()\">Click me</button>\n" +
"<div></div>" "<div></div>\n");
);
page.click("button"); page.click("button");
} }
} }
@ -2050,7 +2117,7 @@ Value to fill for the `<input>`, `<textarea>` or `[contenteditable]` element.
### option: Page.fill.force = %%-input-force-%% ### option: Page.fill.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: Page.fill.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.fill.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.fill.strict = %%-input-strict-%% ### option: Page.fill.strict = %%-input-strict-%%
@ -2110,7 +2177,7 @@ const frame = page.frame({ url: /.*domain.*/ });
``` ```
```java ```java
Frame frame = page.frameByUrl(Pattern.compile(".*domain.*")); Frame frame = page.frameByUrl(Pattern.compile(".*domain.*");
``` ```
```py ```py
@ -2315,7 +2382,7 @@ Attribute name to get the value for.
- returns: <[null]|[Response]> - returns: <[null]|[Response]>
Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
last redirect. If cannot go back, returns `null`. last redirect. If can not go back, returns `null`.
Navigate to the previous page in history. Navigate to the previous page in history.
@ -2333,62 +2400,10 @@ Navigate to the previous page in history.
- returns: <[null]|[Response]> - returns: <[null]|[Response]>
Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
last redirect. If cannot go forward, returns `null`. last redirect. If can not go forward, returns `null`.
Navigate to the next page in history. 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-%% ### option: Page.goForward.waitUntil = %%-navigation-wait-until-%%
* since: v1.8 * since: v1.8
@ -2433,7 +2448,7 @@ Headless mode doesn't support navigation to a PDF document. See the
- `url` <[string]> - `url` <[string]>
URL to navigate page to. The url should include scheme, e.g. `https://`. 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. 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-%% ### option: Page.goto.waitUntil = %%-navigation-wait-until-%%
@ -2463,6 +2478,7 @@ This method hovers over an element matching [`param: selector`] by performing th
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.mouse`] to hover over the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless `noWaitAfter` option is set.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
[TimeoutError]. Passing zero timeout disables this. [TimeoutError]. Passing zero timeout disables this.
@ -2488,10 +2504,10 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.hover.timeout = %%-input-timeout-js-%% ### option: Page.hover.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Page.hover.trial = %%-input-trial-with-modifiers-%% ### option: Page.hover.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
### option: Page.hover.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.hover.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.28 * since: v1.28
## async method: Page.innerHTML ## async method: Page.innerHTML
@ -2640,7 +2656,7 @@ Returns whether the element is [enabled](../actionability.md#enabled).
* discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md). * discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md).
- returns: <[boolean]> - 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-%% ### param: Page.isHidden.selector = %%-input-selector-%%
* since: v1.8 * since: v1.8
@ -2659,7 +2675,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). * discouraged: Use locator-based [`method: Locator.isVisible`] instead. Read more about [locators](../locators.md).
- returns: <[boolean]> - 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-%% ### param: Page.isVisible.selector = %%-input-selector-%%
* since: v1.8 * since: v1.8
@ -2765,7 +2781,8 @@ User can inspect selectors or perform manual steps while paused. Resume will con
the place it was paused. the place it was paused.
:::note :::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 ## async method: Page.pdf
@ -2774,6 +2791,10 @@ This method requires Playwright to be started in a headed mode, with a falsy [`o
Returns the PDF buffer. 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 `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()`: [`method: Page.emulateMedia`] before calling `page.pdf()`:
@ -3138,9 +3159,11 @@ Things to keep in mind:
:::warning :::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. 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. 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. 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 +3184,12 @@ await page.getByRole('button', { name: 'Start here' }).click();
```java ```java
// Setup the handler. // 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(); page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click();
}); });
// Write the test as usual. // Write the test as usual.
page.navigate("https://example.com"); page.goto("https://example.com");
page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
``` ```
@ -3218,12 +3241,12 @@ await page.getByRole('button', { name: 'Start here' }).click();
```java ```java
// Setup the handler. // 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(); page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
}); });
// Write the test as usual. // Write the test as usual.
page.navigate("https://example.com"); page.goto("https://example.com");
page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
``` ```
@ -3275,12 +3298,12 @@ await page.getByRole('button', { name: 'Start here' }).click();
```java ```java
// Setup the handler. // Setup the handler.
page.addLocatorHandler(page.locator("body"), () -> { page.addLocatorHandler(page.locator("body")), () => {
page.evaluate("window.removeObstructionsForTestIfNeeded()"); page.evaluate("window.removeObstructionsForTestIfNeeded()");
}, new Page.AddLocatorHandlerOptions().setNoWaitAfter(true)); }, new Page.AddLocatorHandlerOptions.setNoWaitAfter(true));
// Write the test as usual. // Write the test as usual.
page.navigate("https://example.com"); page.goto("https://example.com");
page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
``` ```
@ -3326,7 +3349,7 @@ await page.addLocatorHandler(page.getByLabel('Close'), async locator => {
``` ```
```java ```java
page.addLocatorHandler(page.getByLabel("Close"), locator -> { page.addLocatorHandler(page.getByLabel("Close"), locator => {
locator.click(); locator.click();
}, new Page.AddLocatorHandlerOptions().setTimes(1)); }, new Page.AddLocatorHandlerOptions().setTimes(1));
``` ```
@ -3365,7 +3388,7 @@ Function that should be run once [`param: locator`] appears. This function shoul
### param: Page.addLocatorHandler.handler ### param: Page.addLocatorHandler.handler
* langs: csharp * langs: csharp
* since: v1.42 * since: v1.42
- `handler` <[function]\([Locator]\): [Promise<any>]> - `handler` <[function]\([Locator]\)>
Function that should be run once [`param: locator`] appears. This function should get rid of the element that blocks actions like click. Function that should be run once [`param: locator`] appears. This function should get rid of the element that blocks actions like click.
@ -3388,32 +3411,6 @@ Specifies the maximum number of times this handler should be called. Unlimited b
By default, after calling the handler Playwright will wait until the overlay becomes hidden, and only then Playwright will continue with the action/assertion that triggered the handler. This option allows to opt-out of this behavior, so that overlay can stay visible after the handler has run. By default, after calling the handler Playwright will wait until the overlay becomes hidden, and only then Playwright will continue with the action/assertion that triggered the handler. This option allows to opt-out of this behavior, so that overlay can stay visible after the handler has run.
## async method: Page.removeAllListeners
* since: v1.47
* langs: js
Removes all the listeners of the given type (or all registered listeners if no type given).
Allows to wait for async listeners to complete or to ignore subsequent errors from these listeners.
**Usage**
```js
page.on('request', async request => {
const response = await request.response();
const body = await response.body();
console.log(body.byteLength);
});
await page.goto('https://playwright.dev', { waitUntil: 'domcontentloaded' });
// Waits for all the reported 'request' events to resolve.
await page.removeAllListeners('request', { behavior: 'wait' });
```
### param: Page.removeAllListeners.type
* since: v1.47
- `type` ?<[string]>
### option: Page.removeAllListeners.behavior = %%-remove-all-listeners-options-behavior-%%
* since: v1.47
## async method: Page.removeLocatorHandler ## async method: Page.removeLocatorHandler
* since: v1.44 * since: v1.44
@ -3608,7 +3605,7 @@ Enabling routing disables http cache.
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> - `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
A glob pattern, regex pattern or predicate receiving [URL] to match while routing. 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. it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
### param: Page.route.handler ### param: Page.route.handler
@ -3668,7 +3665,7 @@ A glob pattern, regular expression or predicate to match the request URL. Only r
* since: v1.32 * since: v1.32
- `updateMode` <[HarMode]<"full"|"minimal">> - `updateMode` <[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 `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`.
### option: Page.routeFromHAR.updateContent ### option: Page.routeFromHAR.updateContent
* since: v1.32 * since: v1.32
@ -3676,88 +3673,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. 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 ## async method: Page.screenshot
* since: v1.8 * since: v1.8
- returns: <[Buffer]> - returns: <[Buffer]>
@ -3857,7 +3772,7 @@ await page.SelectOptionAsync("select#colors", new[] { "red", "green", "blue" });
### option: Page.selectOption.force = %%-input-force-%% ### option: Page.selectOption.force = %%-input-force-%%
* since: v1.13 * since: v1.13
### option: Page.selectOption.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.selectOption.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.selectOption.strict = %%-input-strict-%% ### option: Page.selectOption.strict = %%-input-strict-%%
@ -3894,6 +3809,7 @@ This method checks or unchecks an element matching [`param: selector`] by perfor
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now checked or unchecked. If not, this method throws. 1. Ensure that the element is now checked or unchecked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -3908,7 +3824,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.setChecked.force = %%-input-force-%% ### option: Page.setChecked.force = %%-input-force-%%
* since: v1.15 * since: v1.15
### option: Page.setChecked.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.setChecked.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.15 * since: v1.15
### option: Page.setChecked.position = %%-input-position-%% ### option: Page.setChecked.position = %%-input-position-%%
@ -3982,7 +3898,7 @@ This setting will change the default maximum time for all the methods accepting
* since: v1.8 * since: v1.8
- `timeout` <[float]> - `timeout` <[float]>
Maximum time in milliseconds. Pass `0` to disable timeout. Maximum time in milliseconds
## async method: Page.setExtraHTTPHeaders ## async method: Page.setExtraHTTPHeaders
* since: v1.8 * since: v1.8
@ -4005,7 +3921,6 @@ An object containing additional HTTP headers to be sent with every request. All
Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they Sets the value of the file input to these file paths or files. If some of the `filePaths` are relative paths, then they
are resolved relative to the current working directory. For empty array, clears the selected files. are resolved relative to the current working directory. For empty array, clears the selected files.
For inputs with a `[webkitdirectory]` attribute, only a single directory path is supported.
This method expects [`param: selector`] to point to an This method expects [`param: selector`] to point to an
[input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), targets the control instead. [input element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), targets the control instead.
@ -4016,7 +3931,7 @@ This method expects [`param: selector`] to point to an
### param: Page.setInputFiles.files = %%-input-files-%% ### param: Page.setInputFiles.files = %%-input-files-%%
* since: v1.8 * since: v1.8
### option: Page.setInputFiles.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.setInputFiles.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.setInputFiles.strict = %%-input-strict-%% ### option: Page.setInputFiles.strict = %%-input-strict-%%
@ -4082,16 +3997,12 @@ await page.GotoAsync("https://www.microsoft.com");
### param: Page.setViewportSize.width ### param: Page.setViewportSize.width
* since: v1.10 * since: v1.10
* langs: csharp, java * langs: csharp, java
- `width` <[int]> - `width` <[int]> page width in pixels.
Page width in pixels.
### param: Page.setViewportSize.height ### param: Page.setViewportSize.height
* since: v1.10 * since: v1.10
* langs: csharp, java * langs: csharp, java
- `height` <[int]> - `height` <[int]> page height in pixels.
Page height in pixels.
## async method: Page.tap ## async method: Page.tap
* since: v1.8 * since: v1.8
@ -4104,12 +4015,13 @@ This method taps an element matching [`param: selector`] by performing the follo
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`]. 1. Use [`property: Page.touchscreen`] to tap the center of the element, or the specified [`option: position`].
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
[TimeoutError]. Passing zero timeout disables this. [TimeoutError]. Passing zero timeout disables this.
:::note :::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-%% ### param: Page.tap.selector = %%-input-selector-%%
@ -4121,7 +4033,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.tap.modifiers = %%-input-modifiers-%% ### option: Page.tap.modifiers = %%-input-modifiers-%%
* since: v1.8 * since: v1.8
### option: Page.tap.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.tap.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.tap.position = %%-input-position-%% ### option: Page.tap.position = %%-input-position-%%
@ -4136,7 +4048,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.tap.timeout = %%-input-timeout-js-%% ### option: Page.tap.timeout = %%-input-timeout-js-%%
* since: v1.8 * since: v1.8
### option: Page.tap.trial = %%-input-trial-with-modifiers-%% ### option: Page.tap.trial = %%-input-trial-%%
* since: v1.11 * since: v1.11
## async method: Page.textContent ## async method: Page.textContent
@ -4194,7 +4106,7 @@ A text to type into a focused element.
Time to wait between key presses in milliseconds. Defaults to 0. Time to wait between key presses in milliseconds. Defaults to 0.
### option: Page.type.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.type.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.type.strict = %%-input-strict-%% ### option: Page.type.strict = %%-input-strict-%%
@ -4219,6 +4131,7 @@ This method unchecks an element matching [`param: selector`] by performing the f
set. If the element is detached during the checks, the whole action is retried. set. If the element is detached during the checks, the whole action is retried.
1. Scroll the element into view if needed. 1. Scroll the element into view if needed.
1. Use [`property: Page.mouse`] to click in the center of the element. 1. Use [`property: Page.mouse`] to click in the center of the element.
1. Wait for initiated navigations to either succeed or fail, unless [`option: noWaitAfter`] option is set.
1. Ensure that the element is now unchecked. If not, this method throws. 1. Ensure that the element is now unchecked. If not, this method throws.
When all steps combined have not finished during the specified [`option: timeout`], this method throws a When all steps combined have not finished during the specified [`option: timeout`], this method throws a
@ -4230,7 +4143,7 @@ When all steps combined have not finished during the specified [`option: timeout
### option: Page.uncheck.force = %%-input-force-%% ### option: Page.uncheck.force = %%-input-force-%%
* since: v1.8 * since: v1.8
### option: Page.uncheck.noWaitAfter = %%-input-no-wait-after-removed-%% ### option: Page.uncheck.noWaitAfter = %%-input-no-wait-after-%%
* since: v1.8 * since: v1.8
### option: Page.uncheck.position = %%-input-position-%% ### option: Page.uncheck.position = %%-input-position-%%
@ -4896,7 +4809,7 @@ await page.RunAndWaitForRequestAsync(async () =>
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]> - `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]>
Request URL string, regex or predicate receiving [Request] object. 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. it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
### param: Page.waitForRequest.urlOrPredicate ### param: Page.waitForRequest.urlOrPredicate
@ -4967,7 +4880,6 @@ const response = await responsePromise;
// Alternative way with a predicate. Note no await. // Alternative way with a predicate. Note no await.
const responsePromise = page.waitForResponse(response => const responsePromise = page.waitForResponse(response =>
response.url() === 'https://example.com' && response.status() === 200 response.url() === 'https://example.com' && response.status() === 200
&& response.request().method() === 'GET'
); );
await page.getByText('trigger response').click(); await page.getByText('trigger response').click();
const response = await responsePromise; const response = await responsePromise;
@ -4981,7 +4893,7 @@ Response response = page.waitForResponse("https://example.com/resource", () -> {
}); });
// Waits for the next response matching some conditions // Waits for the next response matching some conditions
Response response = page.waitForResponse(response -> "https://example.com".equals(response.url()) && response.status() == 200 && "GET".equals(response.request().method()), () -> { Response response = page.waitForResponse(response -> "https://example.com".equals(response.url()) && response.status() == 200, () -> {
// Triggers the response // Triggers the response
page.getByText("trigger response").click(); page.getByText("trigger response").click();
}); });
@ -4994,7 +4906,7 @@ response = await response_info.value
return response.ok return response.ok
# or with a lambda # or with a lambda
async with page.expect_response(lambda response: response.url == "https://example.com" and response.status == 200 and response.request.method == "get") as response_info: async with page.expect_response(lambda response: response.url == "https://example.com" and response.status == 200) as response_info:
await page.get_by_text("trigger response").click() await page.get_by_text("trigger response").click()
response = await response_info.value response = await response_info.value
return response.ok return response.ok
@ -5007,7 +4919,7 @@ response = response_info.value
return response.ok return response.ok
# or with a lambda # or with a lambda
with page.expect_response(lambda response: response.url == "https://example.com" and response.status == 200 and response.request.method == "get") as response_info: with page.expect_response(lambda response: response.url == "https://example.com" and response.status == 200) as response_info:
page.get_by_text("trigger response").click() page.get_by_text("trigger response").click()
response = response_info.value response = response_info.value
return response.ok return response.ok
@ -5024,7 +4936,7 @@ await page.RunAndWaitForResponseAsync(async () =>
await page.RunAndWaitForResponseAsync(async () => await page.RunAndWaitForResponseAsync(async () =>
{ {
await page.GetByText("trigger response").ClickAsync(); await page.GetByText("trigger response").ClickAsync();
}, response => response.Url == "https://example.com" && response.Status == 200 && response.Request.Method == "GET"); }, response => response.Url == "https://example.com" && response.Status == 200);
``` ```
## async method: Page.waitForResponse ## async method: Page.waitForResponse
@ -5040,7 +4952,7 @@ await page.RunAndWaitForResponseAsync(async () =>
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]> - `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]>
Request URL string, regex or predicate receiving [Response] object. 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. it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
### param: Page.waitForResponse.urlOrPredicate ### param: Page.waitForResponse.urlOrPredicate
@ -5049,7 +4961,7 @@ it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/We
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]|[Promise]<[boolean]>> - `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]|[Promise]<[boolean]>>
Request URL string, regex or predicate receiving [Response] object. 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. it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
### option: Page.waitForResponse.timeout ### option: Page.waitForResponse.timeout

View file

@ -14,14 +14,14 @@ test('navigates to login', async ({ page }) => {
``` ```
```java ```java
// ... ...
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestPage { public class TestPage {
// ... ...
@Test @Test
void navigatesToLoginPage() { void navigatesToLoginPage() {
// ... ...
page.getByText("Sign in").click(); page.getByText("Sign in").click();
assertThat(page).hasURL(Pattern.compile(".*/login")); assertThat(page).hasURL(Pattern.compile(".*/login"));
} }
@ -50,19 +50,21 @@ def test_navigates_to_login_page(page: Page) -> None:
```csharp ```csharp
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Playwright; using System.Threading.Tasks;
using Microsoft.Playwright.MSTest; using Microsoft.Playwright.NUnit;
using NUnit.Framework;
namespace PlaywrightTests; namespace PlaywrightTests;
[TestClass] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[TestMethod] [Test]
public async Task NavigateToLoginPage() public async Task NavigatetoLoginPage()
{ {
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync(); // ..
await Expect(Page).ToHaveURLAsync(new Regex(".*/login")); await Page.GetByText("Sing in").ClickAsync();
await Expect(Page.Locator("div#foobar")).ToHaveURL(new Regex(".*/login"));
} }
} }
``` ```
@ -83,7 +85,7 @@ assertThat(page).not().hasURL("error");
``` ```
```csharp ```csharp
await Expect(Page).Not.ToHaveURLAsync("error"); await Expect(Page).Not.ToHaveURL("error");
``` ```
## async method: PageAssertions.NotToHaveTitle ## async method: PageAssertions.NotToHaveTitle
@ -271,7 +273,7 @@ expect(page).to_have_title(re.compile(r".*checkout"))
``` ```
```csharp ```csharp
await Expect(Page).ToHaveTitleAsync("Playwright"); await Expect(Page).ToHaveTitle("Playwright");
``` ```
### param: PageAssertions.toHaveTitle.titleOrRegExp ### param: PageAssertions.toHaveTitle.titleOrRegExp
@ -296,18 +298,7 @@ Ensures the page is navigated to the given URL.
**Usage** **Usage**
```js ```js
// Check for the page URL to be 'https://playwright.dev/docs/intro' (including query string) await expect(page).toHaveURL(/.*checkout/);
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';
});
``` ```
```java ```java
@ -331,21 +322,20 @@ expect(page).to_have_url(re.compile(".*checkout"))
``` ```
```csharp ```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 * since: v1.18
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]> - `urlOrRegExp` <[string]|[RegExp]>
Expected URL string, RegExp, or predicate receiving [URL] to match. Expected URL string or RegExp.
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.
### option: PageAssertions.toHaveURL.ignoreCase ### option: PageAssertions.toHaveURL.ignoreCase
* since: v1.44 * since: v1.44
- `ignoreCase` <[boolean]> - `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-%% ### option: PageAssertions.toHaveURL.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18

View file

@ -35,13 +35,14 @@ def test_status_becomes_submitted(page: Page) -> None:
``` ```
```java ```java
...
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestExample { public class TestExample {
// ... ...
@Test @Test
void statusBecomesSubmitted() { void statusBecomesSubmitted() {
// ... ...
page.locator("#submit-button").click(); page.locator("#submit-button").click();
assertThat(page.locator(".status")).hasText("Submitted"); assertThat(page.locator(".status")).hasText("Submitted");
} }
@ -49,18 +50,19 @@ public class TestExample {
``` ```
```csharp ```csharp
using Microsoft.Playwright; using System.Threading.Tasks;
using Microsoft.Playwright.MSTest; using Microsoft.Playwright.NUnit;
using NUnit.Framework;
namespace PlaywrightTests; namespace PlaywrightTests;
[TestClass] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[TestMethod] [Test]
public async Task StatusBecomesSubmitted() public async Task StatusBecomesSubmitted()
{ {
await Page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).ClickAsync(); await Page.Locator("#submit-button").ClickAsync();
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted"); await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");
} }
} }

View file

@ -124,7 +124,7 @@ Headers with multiple entries, such as `Set-Cookie`, appear in the array multipl
* since: v1.15 * since: v1.15
- returns: <[null]|[string]> - returns: <[null]|[string]>
Returns the value of the header matching the name. The name is case-insensitive. Returns the value of the header matching the name. The name is case insensitive.
### param: Request.headerValue.name ### param: Request.headerValue.name
* since: v1.15 * since: v1.15

View file

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

View file

@ -59,7 +59,7 @@ Headers with multiple entries, such as `Set-Cookie`, appear in the array multipl
* since: v1.15 * since: v1.15
- returns: <[null]|[string]> - returns: <[null]|[string]>
Returns the value of the header matching the name. The name is case-insensitive. If multiple headers have Returns the value of the header matching the name. The name is case insensitive. If multiple headers have
the same name (except `set-cookie`), they are returned as a list separated by `, `. For `set-cookie`, the `\n` separator is used. If no headers are found, `null` is returned. the same name (except `set-cookie`), they are returned as a list separated by `, `. For `set-cookie`, the `\n` separator is used. If no headers are found, `null` is returned.
### param: Response.headerValue.name ### param: Response.headerValue.name
@ -72,7 +72,7 @@ Name of the header.
* since: v1.15 * since: v1.15
- returns: <[Array]<[string]>> - returns: <[Array]<[string]>>
Returns all values of the headers matching the name, for example `set-cookie`. The name is case-insensitive. Returns all values of the headers matching the name, for example `set-cookie`. The name is case insensitive.
### param: Response.headerValues.name ### param: Response.headerValues.name
* since: v1.15 * since: v1.15

View file

@ -39,7 +39,7 @@ Optional error code. Defaults to `failed`, could be one of the following:
- alias-java: resume - alias-java: resume
- alias-python: continue_ - alias-python: continue_
Sends route's request to the network with optional overrides. Continues route's request with optional overrides.
**Usage** **Usage**
@ -102,9 +102,7 @@ await page.RouteAsync("**/*", async route =>
**Details** **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.
### option: Route.continue.url ### option: Route.continue.url
* since: v1.8 * since: v1.8
@ -148,15 +146,13 @@ If set changes the request HTTP headers. Header values will be converted to a st
## async method: Route.fallback ## async method: Route.fallback
* since: v1.23 * since: v1.23
Continues route's request with optional overrides. The method is similar to [`method: Route.continue`] with the difference that other matching handlers will be invoked before sending the request.
**Usage**
When several routes match the given pattern, they run in the order opposite to their registration. When several routes match the given pattern, they run in the order opposite to their registration.
That way the last registered route can always override all the previous ones. In the example below, That way the last registered route can always override all the previous ones. In the example below,
request will be handled by the bottom-most handler first, then it'll fall back to the previous one and request will be handled by the bottom-most handler first, then it'll fall back to the previous one and
in the end will be aborted by the first registered route. in the end will be aborted by the first registered route.
**Usage**
```js ```js
await page.route('**/*', async route => { await page.route('**/*', async route => {
// Runs last. // Runs last.
@ -390,8 +386,6 @@ await page.RouteAsync("**/*", async route =>
}); });
``` ```
Use [`method: Route.continue`] to immediately send the request to the network, other matching handlers won't be invoked in that case.
### option: Route.fallback.url ### option: Route.fallback.url
* since: v1.23 * since: v1.23
- `url` <[string]> - `url` <[string]>
@ -509,12 +503,6 @@ If set changes the request URL. New URL must have same protocol as original one.
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded. Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
Defaults to `20`. Pass `0` to not follow redirects. Defaults to `20`. Pass `0` to not follow redirects.
### option: Route.fetch.maxRetries
* since: v1.46
- `maxRetries` <[int]>
Maximum number of times network errors should be retried. Currently only `ECONNRESET` error is retried. Does not retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
### option: Route.fetch.timeout ### option: Route.fetch.timeout
* since: v1.33 * since: v1.33
- `timeout` <[float]> - `timeout` <[float]>

View file

@ -4,25 +4,19 @@
The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the 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. 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 ## async method: Touchscreen.tap
* since: v1.8 * since: v1.8
Dispatches a `touchstart` and `touchend` event with a single touch at the position ([`param: x`],[`param: y`]). Dispatches a `touchstart` and `touchend` event with a single touch at the position ([`param: x`],[`param: y`]).
:::note :::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 ### param: Touchscreen.tap.x
* since: v1.8 * since: v1.8
- `x` <[float]> - `x` <[float]>
X coordinate relative to the main frame's viewport in CSS pixels.
### param: Touchscreen.tap.y ### param: Touchscreen.tap.y
* since: v1.8 * since: v1.8
- `y` <[float]> - `y` <[float]>
Y coordinate relative to the main frame's viewport in CSS pixels.

View file

@ -121,7 +121,7 @@ await context.Tracing.StopAsync(new()
- `name` <[string]> - `name` <[string]>
If specified, intermediate trace files are going to be saved into the files with the 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 To specify the final trace zip file name, you need to pass `path` option to
[`method: Tracing.stop`] instead. [`method: Tracing.stop`] instead.
@ -277,84 +277,10 @@ Trace name to be shown in the Trace Viewer.
- `name` <[string]> - `name` <[string]>
If specified, intermediate trace files are going to be saved into the files with the 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 To specify the final trace zip file name, you need to pass `path` option to
[`method: Tracing.stopChunk`] instead. [`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 ## async method: Tracing.stop
* since: v1.12 * since: v1.12

View file

@ -1,9 +1,7 @@
# class: WebSocket # class: WebSocket
* since: v1.8 * 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. The [WebSocket] class represents websocket connections in the page.
If you want to intercept or modify WebSocket frames, consider using [WebSocketRoute].
## event: WebSocket.close ## event: WebSocket.close
* since: v1.8 * since: v1.8

View file

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

View file

@ -62,19 +62,12 @@ Maximum time in milliseconds. Defaults to `0` - no timeout. The default value ca
[`method: Page.setDefaultTimeout`] methods. [`method: Page.setDefaultTimeout`] methods.
## input-no-wait-after ## input-no-wait-after
* deprecated: This option will default to `true` in the future.
- `noWaitAfter` <[boolean]> - `noWaitAfter` <[boolean]>
Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating
to inaccessible pages. Defaults to `false`. to inaccessible pages. Defaults to `false`.
## input-no-wait-after-removed
* deprecated: This option has no effect.
- `noWaitAfter` <[boolean]>
This option has no effect.
## input-force ## input-force
- `force` <[boolean]> - `force` <[boolean]>
@ -136,11 +129,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. 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 ## input-source-position
- `sourcePosition` <[Object]> - `sourcePosition` <[Object]>
- `x` <[float]> - `x` <[float]>
@ -259,12 +247,11 @@ Specify environment variables that will be visible to the browser. Defaults to `
- `httpOnly` <[boolean]> - `httpOnly` <[boolean]>
- `secure` <[boolean]> - `secure` <[boolean]>
- `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">> sameSite flag - `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">> sameSite flag
- `origins` <[Array]<[Object]>> - `origins` <[Array]<[Object]>> localStorage to set for context
- `origin` <[string]> - `origin` <[string]>
- `localStorage` <[Array]<[Object]>> localStorage to set for context - `localStorage` <[Array]<[Object]>>
- `name` <[string]> - `name` <[string]>
- `value` <[string]> - `value` <[string]>
- `indexedDB` ?<[Array]<[unknown]>> indexedDB to set for context
Learn more about [storage state and auth](../auth.md). Learn more about [storage state and auth](../auth.md).
@ -362,15 +349,9 @@ Emulates consistent window screen size available inside web page via `window.scr
Target URL. Target URL.
## js-fetch-option-params ## js-python-fetch-option-params
* langs: js * langs: js, python
- `params` <[Object]<[string], [string]|[float]|[boolean]>|[URLSearchParams]|[string]> - `params` <[Object]<[string], [string]|[float]|[boolean]>>
Query parameters to be sent with the URL.
## python-fetch-option-params
* langs: python
- `params` <[Object]<[string], [string]|[float]|[boolean]>|[string]>
Query parameters to be sent with the URL. Query parameters to be sent with the URL.
@ -380,13 +361,7 @@ Query parameters to be sent with the URL.
Query parameters to be sent with the URL. Query parameters to be sent with the URL.
## csharp-fetch-option-paramsString ## java-csharp-fetch-params
* langs: csharp
- `paramsString` <[string]>
Query parameters to be sent with the URL.
## java-fetch-params
* langs: java * langs: java
- `options` ?<[RequestOptions]> - `options` ?<[RequestOptions]>
@ -411,16 +386,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 Whether to throw on response codes other than 2xx and 3xx. By default response object is returned
for all status codes. for all status codes.
## js-fetch-option-form ## js-python-fetch-option-form
* langs: js * langs: js, python
- `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
- `form` <[Object]<[string], [string]|[float]|[boolean]>> - `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 Provides an object that will be serialized as html form using `application/x-www-form-urlencoded` encoding and sent as
@ -491,12 +458,6 @@ Whether to ignore HTTPS errors when sending network requests. Defaults to `false
Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded. Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded.
Defaults to `20`. Pass `0` to not follow redirects. Defaults to `20`. Pass `0` to not follow redirects.
## js-python-csharp-fetch-option-maxretries
* langs: js, python, csharp
- `maxRetries` <[int]>
Maximum number of times network errors should be retried. Currently only `ECONNRESET` error is retried. Does not retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries.
## evaluate-expression ## evaluate-expression
- `expression` <[string]> - `expression` <[string]>
@ -547,27 +508,6 @@ Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `no_
Does not enforce fixed viewport, allows resizing window in the headed mode. Does not enforce fixed viewport, allows resizing window in the headed mode.
## context-option-clientCertificates
- `clientCertificates` <[Array]<[Object]>>
- `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
- `certPath` ?<[path]> Path to the file with the certificate in PEM format.
- `cert` ?<[Buffer]> Direct value of the certificate in PEM format.
- `keyPath` ?<[path]> Path to the file with the private key in PEM format.
- `key` ?<[Buffer]> Direct value of the private key in PEM format.
- `pfxPath` ?<[path]> Path to the PFX or PKCS12 encoded private key and certificate chain.
- `pfx` ?<[Buffer]> Direct value of the PFX or PKCS12 encoded private key and certificate chain.
- `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX).
TLS Client Authentication allows the server to request a client certificate and verify it.
**Details**
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
When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`.
:::
## context-option-useragent ## context-option-useragent
- `userAgent` <[string]> - `userAgent` <[string]>
@ -631,7 +571,6 @@ Whether to emulate network being offline. Defaults to `false`. Learn more about
- `username` <[string]> - `username` <[string]>
- `password` <[string]> - `password` <[string]>
- `origin` ?<[string]> Restrain sending http credentials on specific origin (scheme://host:port). - `origin` ?<[string]> Restrain sending http credentials on specific origin (scheme://host:port).
- `send` ?<[HttpCredentialsSend]<"unauthorized"|"always">> This option only applies to the requests sent from corresponding [APIRequestContext] and does not affect requests sent from the browser. `'always'` - `Authorization` header with basic authentication credentials will be sent with the each API request. `'unauthorized` - the credentials are only sent when 401 (Unauthorized) response with `WWW-Authenticate` header is received. Defaults to `'unauthorized'`.
Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
If no origin is specified, the username and password are sent to any servers upon unauthorized responses. If no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@ -640,14 +579,14 @@ If no origin is specified, the username and password are sent to any servers upo
* langs: js, java * langs: js, java
- `colorScheme` <null|[ColorScheme]<"light"|"dark"|"no-preference">> - `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'`. [`method: Page.emulateMedia`] for more details. Passing `null` resets emulation to system defaults. Defaults to `'light'`.
## context-option-colorscheme-csharp-python ## context-option-colorscheme-csharp-python
* langs: csharp, python * langs: csharp, python
- `colorScheme` <[ColorScheme]<"light"|"dark"|"no-preference"|"null">> - `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'`. [`method: Page.emulateMedia`] for more details. Passing `'null'` resets emulation to system defaults. Defaults to `'light'`.
## context-option-reducedMotion ## context-option-reducedMotion
@ -674,18 +613,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'`. 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 ## context-option-logger
* langs: js * langs: js
- `logger` <[Logger]> - `logger` <[Logger]>
@ -712,7 +639,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. - `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. - `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`. - `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 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 specified, the HAR is not recorded. Make sure to await [`method: BrowserContext.close`] for the HAR to be
@ -778,6 +705,8 @@ not recorded. Make sure to call [`method: BrowserContext.close`] for videos to b
* langs: csharp, java, python * langs: csharp, java, python
- alias-python: record_video_size - alias-python: record_video_size
- `recordVideoSize` <[Object]> - `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. - `width` <[int]> Video frame width.
- `height` <[int]> Video frame height. - `height` <[int]> Video frame height.
@ -795,6 +724,12 @@ Actual picture of each page will be scaled down if necessary to fit the specifie
Network proxy settings to use with this context. Defaults to none. Network proxy settings to use with this context. Defaults to none.
:::note
For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all
contexts override the proxy, global proxy will be never used and can be any string, for example
`launch({ proxy: { server: 'http://per-context' } })`.
:::
## context-option-strict ## context-option-strict
- `strictSelectors` <[boolean]> - `strictSelectors` <[boolean]>
@ -810,27 +745,16 @@ Whether to allow sites to register Service workers. Defaults to `'allow'`.
* `'allow'`: [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) can be registered. * `'allow'`: [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) can be registered.
* `'block'`: Playwright will block all registration of Service Workers. * `'block'`: Playwright will block all registration of Service Workers.
## remove-all-listeners-options-behavior
* langs: js
* since: v1.47
- `behavior` <[RemoveAllListenersBehavior]<"wait"|"ignoreErrors"|"default">>
Specifies whether to wait for already running listeners and what to do if they throw errors:
* `'default'` - do not wait for current listener calls (if any) to finish, if the listener throws, it may result in unhandled error
* `'wait'` - wait for current listener calls (if any) to finish
* `'ignoreErrors'` - do not wait for current listener calls (if any) to finish, all errors thrown by the listeners after removal are silently caught
## unroute-all-options-behavior ## unroute-all-options-behavior
* langs: js, csharp, python * langs: js, csharp, python
* since: v1.41 * since: v1.41
- `behavior` <[UnrouteBehavior]<"wait"|"ignoreErrors"|"default">> - `behavior` <[UnrouteBehavior]<"wait"|"ignoreErrors"|"default">>
Specifies whether to wait for already running handlers and what to do if they throw errors: Specifies wether to wait for already running handlers and what to do if they throw errors:
* `'default'` - do not wait for current handler calls (if any) to finish, if unrouted handler throws, it may result in unhandled error * `'default'` - do not wait for current handler calls (if any) to finish, if unrouted handler throws, it may result in unhandled error
* `'wait'` - wait for current handler calls (if any) to finish * `'wait'` - wait for current handler calls (if any) to finish
* `'ignoreErrors'` - do not wait for current handler calls (if any) to finish, all errors thrown by the handlers after unrouting are silently caught * `'ignoreErrors'` - do not wait for current handler calls (if any) to finish, all errors thrown by the handlers after unrouting are silently caught
## select-options-values ## select-options-values
* langs: java, js, csharp * langs: java, js, csharp
- `values` <[null]|[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>> - `values` <[null]|[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>>
@ -986,8 +910,6 @@ between the same pixel in compared images, between zero (strict) and one (lax),
- %%-context-option-reducedMotion-csharp-python-%% - %%-context-option-reducedMotion-csharp-python-%%
- %%-context-option-forcedColors-%% - %%-context-option-forcedColors-%%
- %%-context-option-forcedColors-csharp-python-%% - %%-context-option-forcedColors-csharp-python-%%
- %%-context-option-contrast-%%
- %%-context-option-contrast-csharp-python-%%
- %%-context-option-logger-%% - %%-context-option-logger-%%
- %%-context-option-videospath-%% - %%-context-option-videospath-%%
- %%-context-option-videosize-%% - %%-context-option-videosize-%%
@ -1016,11 +938,7 @@ Additional arguments to pass to the browser instance. The list of Chromium flags
## browser-option-channel ## browser-option-channel
- `channel` <[string]> - `channel` <[string]>
Browser distribution channel. 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).
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-option-chromiumsandbox ## browser-option-chromiumsandbox
- `chromiumSandbox` <[boolean]> - `chromiumSandbox` <[boolean]>
@ -1063,7 +981,7 @@ Close the browser process on SIGHUP. Defaults to `true`.
Whether to run browser in headless mode. More details for Whether to run browser in headless mode. More details for
[Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and [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 [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 ## js-python-browser-option-firefoxuserprefs
* langs: js, python * langs: js, python
@ -1155,11 +1073,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. 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-options-list-v1.14
- %%-locator-option-has-text-%% - %%-locator-option-has-text-%%
- %%-locator-option-has-%% - %%-locator-option-has-%%
@ -1210,7 +1123,6 @@ Specify screenshot type, defaults to `png`.
Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with 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. 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 ## screenshot-option-mask-color
* since: v1.35 * since: v1.35
@ -1513,19 +1425,19 @@ page.get_by_text(re.compile("^hello$", re.IGNORECASE))
```java ```java
// Matches <span> // Matches <span>
page.getByText("world"); page.getByText("world")
// Matches first <div> // Matches first <div>
page.getByText("Hello world"); page.getByText("Hello world")
// Matches second <div> // Matches second <div>
page.getByText("Hello", new Page.GetByTextOptions().setExact(true)); page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
// Matches both <div>s // Matches both <div>s
page.getByText(Pattern.compile("Hello")); page.getByText(Pattern.compile("Hello"))
// Matches second <div> // Matches second <div>
page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)); page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
``` ```
```csharp ```csharp
@ -1779,9 +1691,7 @@ await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues");
- `type` ?<[string]> - `type` ?<[string]>
* langs: js * 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`]. This option configures a template controlling location of snapshots generated by [`method: PageAssertions.toHaveScreenshot#1`] and [`method: SnapshotAssertions.toMatchSnapshot#1`].
You can configure templates for each assertion separately in [`property: TestConfig.expect`].
**Usage** **Usage**
@ -1790,19 +1700,7 @@ import { defineConfig } from '@playwright/test';
export default defineConfig({ export default defineConfig({
testDir: './tests', testDir: './tests',
// Single template for all assertions
snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}', 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 +1731,22 @@ test.describe('suite', () => {
The list of supported tokens: 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` * Value: `foo/bar/baz`
* `{ext}` - Snapshot extension (with the leading dot). * `{ext}` - snapshot extension (with dots)
* Value: `.png` * Value: `.png`
* `{platform}` - The value of `process.platform`. * `{platform}` - The value of `process.platform`.
* `{projectName}` - Project's file-system-sanitized name, if any. * `{projectName}` - Project's file-system-sanitized name, if any.
* Value: `''` (empty string). * 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`) * Value: `/home/playwright/tests` (since `snapshotDir` is not provided in config, it defaults to `testDir`)
* `{testDir}` - Project's [`property: TestProject.testDir`]. * `{testDir}` - Project's [`property: TestConfig.testDir`].
* Value: `/home/playwright/tests` (absolute path since `testDir` is resolved relative to directory with config) * 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**. * `{testFileDir}` - Directories in relative path from `testDir` to **test file**.
* Value: `page` * Value: `page`
* `{testFileName}` - Test file name with extension. * `{testFileName}` - Test file name with extension.
* Value: `page-click.spec.ts` * 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` * Value: `page/page-click.spec.ts`
* `{testName}` - File-system-sanitized test title, including parent describes but excluding file name. * `{testName}` - File-system-sanitized test title, including parent describes but excluding file name.
* Value: `suite-test-should-work` * Value: `suite-test-should-work`

View file

@ -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]
```

View file

@ -15,7 +15,7 @@ We recommend to create `playwright/.auth` directory and add it to your `.gitigno
```bash tab=bash-bash ```bash tab=bash-bash
mkdir -p playwright/.auth mkdir -p playwright/.auth
echo $'\nplaywright/.auth' >> .gitignore echo "\nplaywright/.auth" >> .gitignore
``` ```
```batch tab=bash-batch ```batch tab=bash-batch
@ -47,9 +47,8 @@ Create `tests/auth.setup.ts` that will prepare authenticated browser state for a
```js title="tests/auth.setup.ts" ```js title="tests/auth.setup.ts"
import { test as setup, expect } from '@playwright/test'; import { test as setup, expect } from '@playwright/test';
import path from 'path';
const authFile = path.join(__dirname, '../playwright/.auth/user.json'); const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => { setup('authenticate', async ({ page }) => {
// Perform authentication steps. Replace these actions with your own. // Perform authentication steps. Replace these actions with your own.
@ -114,8 +113,6 @@ test('test', async ({ page }) => {
}); });
``` ```
Note that you need to delete the stored state when it expires. If you don't need to keep the state between test runs, write the browser state under [`property: TestProject.outputDir`], which is automatically cleaned up before every test run.
### Authenticating in UI mode ### Authenticating in UI mode
* langs: js * langs: js
@ -232,7 +229,7 @@ await page.goto('https://github.com/login')
# Interact with login form # Interact with login form
await page.get_by_label("Username or email address").fill("username") await page.get_by_label("Username or email address").fill("username")
await page.get_by_label("Password").fill("password") 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 # Continue with the test
``` ```
@ -266,9 +263,9 @@ existing authentication state instead.
Playwright provides a way to reuse the signed-in state in the tests. That way you can log 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. 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 pre-populated 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. The following code snippet retrieves state from an authenticated context and creates a new context with that state.
@ -583,7 +580,7 @@ test('admin and user', async ({ adminPage, userPage }) => {
### Session storage ### 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 ```js
// Get session storage and store as env variable // Get session storage and store as env variable

View file

@ -43,7 +43,7 @@ You can also reuse the signed-in state in the tests with [setup project](./auth.
### Avoid testing third-party dependencies ### Avoid testing third-party dependencies
Only test what you control. Don't try to test links to external sites or third party servers that you do not control. Not only is it time consuming and can slow down your tests but also you cannot control the content of the page you are linking to, or if there are cookie banners or overlay pages or anything else that might cause your test to fail. Only test what you control. Don't try to test links to external sites or third party servers that you do not control. Not only is it time consuming and can slow down your tests but also you can not control the content of the page you are linking to, or if there are cookie banners or overlay pages or anything else that might cause your test to fail.
Instead, use the [Playwright Network API](/network.md#handle-requests) and guarantee the response needed. Instead, use the [Playwright Network API](/network.md#handle-requests) and guarantee the response needed.
@ -90,7 +90,7 @@ await page
#### Prefer user-facing attributes to XPath or CSS selectors #### 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 ```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. 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 ```bash
npx playwright codegen playwright.dev 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. 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. 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. 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 ```bash
npx playwright test --debug 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. 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" /> <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. 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 ```bash
npx playwright test example.spec.ts:9 --debug 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 #### 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. 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,79 +193,18 @@ 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. 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 ```bash
npx playwright test --trace on 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. 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 ```bash
npx playwright show-report 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" /> <img width="1516" alt="Playwrights HTML report" loading="lazy" src="https://user-images.githubusercontent.com/13063165/212279022-d929d4c0-2271-486a-a75f-166ac231d25f.png" />
Traces can be opened by clicking on the icon next to the test file name or by opening each of the test reports and scrolling down to the traces section. Traces can be opened by clicking on the icon next to the test or by opening each of the test reports and scrolling down to the traces section.
<img width="1516" alt="Screenshot 2023-01-13 at 09 58 34" loading="lazy" src="https://user-images.githubusercontent.com/13063165/212279699-c9eb134f-4f4e-4f19-805c-37596d3272a6.png" /> <img width="1516" alt="Screenshot 2023-01-13 at 09 58 34" loading="lazy" src="https://user-images.githubusercontent.com/13063165/212279699-c9eb134f-4f4e-4f19-805c-37596d3272a6.png" />
@ -365,8 +214,8 @@ Playwright comes with a range of tooling to help you write tests.
- The [VS Code extension](./getting-started-vscode.md) gives you a great developer experience when writing, running, and debugging tests. - The [VS Code extension](./getting-started-vscode.md) gives you a great developer experience when writing, running, and debugging tests.
- The [test generator](./codegen.md) can generate tests and pick locators for you. - The [test generator](./codegen.md) can generate tests and pick locators for you.
- The [trace viewer](./trace-viewer.md) gives you a full trace of your tests as a local PWA that can easily be shared. With the trace viewer you can view the timeline, inspect DOM snapshots for each action, view network requests and more. - The [trace viewer](./trace-viewer.md) gives you a full trace of your tests as a local PWA that can easily be shared. With the trace viewer you can view the timeline, inspect DOM snapshots for each action, view network requests and more.
- The [UI Mode](./test-ui-mode) lets you explore, run and debug tests with a time travel experience complete with watch mode. All test files are loaded into the testing sidebar where you can expand each file and describe block to individually run, view, watch and debug each test. - The [UI Mode](./test-ui-mode) let's you explore, run and debug tests with a time travel experience complete with watch mode. All test files are loaded into the testing sidebar where you can expand each file and describe block to individually run, view, watch and debug each test.
- [TypeScript](./test-typescript) in Playwright works out of the box and gives you better IDE integrations. Your IDE will show you everything you can do and highlight when you do something wrong. No TypeScript experience is needed and it is not necessary for your code to be in TypeScript, all you need to do is create your tests with a `.ts` extension. - [Typescript](./test-typescript) in Playwright works out of the box and gives you better IDE integrations. Your IDE will show you everything you can do and highlight when you do something wrong. No TypeScript experience is needed and it is not necessary for your code to be in TypeScript, all you need to do is create your tests with a `.ts` extension.
### Test across all browsers ### Test across all browsers
@ -397,102 +246,26 @@ 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. 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 ```bash
npm install -D @playwright/test@latest 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. 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. 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 ```bash
npx playwright --version 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 ### 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. 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. 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.
#### 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 ### 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. Linting the tests helps catching 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.
### Use parallelism and sharding ### Use parallelism and sharding
@ -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. 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 ```bash
npx playwright test --shard=1/3 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 ## Productivity tips
### Use Soft assertions ### Use Soft assertions

View file

@ -230,13 +230,13 @@ Running 1 test using 1 worker
✓ [firefox] example.spec.ts:3:1 basic test (2s) ✓ [firefox] example.spec.ts:3:1 basic test (2s)
``` ```
With the VS Code extension you can run your tests on different browsers by checking the checkbox next to the browser name in the Playwright sidebar. These names are defined in your Playwright config file under the projects section. The default config when installing Playwright gives you 3 projects, Chromium, Firefox and WebKit. The first project is selected by default. The VS Code test runner runs your tests on the default browser of Chrome. To run on other/multiple browsers click the play button's dropdown from the testing sidebar and choose another profile or modify the default profile by clicking **Select Default Profile** and select the browsers you wish to run your tests on.
![Projects section in VS Code extension](https://github.com/microsoft/playwright/assets/13063165/58fedea6-a2b9-4942-b2c7-2f3d482210cf) <img width="1464" alt="selecting browsers" src="https://user-images.githubusercontent.com/13063165/221136731-9d4bc18f-38a4-4adb-997b-5b98c98aec7f.png" />
To run tests on multiple projects(browsers), select each project by checking the checkboxes next to the project name. Choose a specific profile, various profiles or all profiles to run tests on.
![Selecting projects to run tests on](https://github.com/microsoft/playwright/assets/13063165/6dc86ef4-6097-481c-9cab-b6e053ec7ea6) <img width="1536" alt="choosing default profiles" src="https://user-images.githubusercontent.com/13063165/221669537-e5df8672-f50d-4ff1-96f9-141cd67e12f8.png" />
### Run tests on different browsers ### Run tests on different browsers
* langs: python * langs: python
@ -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. 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 ### 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. 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 :::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. 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 ```js
import { defineConfig, devices } from '@playwright/test'; 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 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 #### Installing Google Chrome & Microsoft Edge
If Google Chrome or Microsoft Edge is not available on your machine, you can install 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. 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 ### 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. Playwright's WebKit version matches the recent WebKit trunk build, before it is used in 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 against the 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.
## Install behind a firewall or a proxy ## Install behind a firewall or a proxy
@ -732,24 +604,6 @@ $Env:PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT="120000"
pwsh bin/Debug/netX/playwright.ps1 install pwsh bin/Debug/netX/playwright.ps1 install
``` ```
If you are [installing dependencies](#install-system-dependencies) and need to use a proxy on Linux, make sure to run the command as a root user. Otherwise, Playwright will attempt to become a root and will not pass environment variables like `HTTPS_PROXY` to the linux package manager.
```bash js
sudo HTTPS_PROXY=https://192.0.2.1 npx playwright install-deps
```
```bash java
sudo HTTPS_PROXY=https://192.0.2.1 mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps"
```
```bash python
sudo HTTPS_PROXY=https://192.0.2.1 playwright install-deps
```
```bash csharp
sudo HTTPS_PROXY=https://192.0.2.1 pwsh bin/Debug/netX/playwright.ps1 install-deps
```
## Download from artifact repository ## Download from artifact repository
By default, Playwright downloads browsers from Microsoft's CDN. By default, Playwright downloads browsers from Microsoft's CDN.
@ -891,7 +745,7 @@ pwsh bin/Debug/netX/playwright.ps1 install
Playwright downloads Chromium, WebKit and Firefox browsers into the OS-specific cache folders: Playwright downloads Chromium, WebKit and Firefox browsers into the OS-specific cache folders:
- `%USERPROFILE%\AppData\Local\ms-playwright` on Windows - `%USERPROFILE%\AppData\Local\ms-playwright` on Windows
- `~/Library/Caches/ms-playwright` on macOS - `~/Library/Caches/ms-playwright` on MacOS
- `~/.cache/ms-playwright` on Linux - `~/.cache/ms-playwright` on Linux
These browsers will take a few hundred megabytes of disk space when installed: These browsers will take a few hundred megabytes of disk space when installed:

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