Merge branch 'main' into webserver-sigkill

This commit is contained in:
Simon Knott 2024-12-20 15:24:47 +01:00
commit c6cf6bcabf
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
437 changed files with 12266 additions and 6344 deletions

View file

@ -1,11 +0,0 @@
{
"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"
]
}

14
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"

View file

@ -35,7 +35,7 @@ jobs:
run: |
npx playwright merge-reports --config .github/workflows/merge.config.ts ./all-blob-reports
env:
NODE_OPTIONS: --max-old-space-size=4096
NODE_OPTIONS: --max-old-space-size=8192
- name: Azure Login
uses: azure/login@v2

View file

@ -27,7 +27,11 @@ jobs:
repo: context.repo.repo,
commit_sha: context.sha,
});
const commitHeader = data.message.split('\n')[0].replace(/#(\d+)/g, 'https://github.com/microsoft/playwright/pull/$1');
const commitHeader = data.message.split('\n')[0];
const prMatch = commitHeader.match(/#(\d+)/);
const formattedCommit = prMatch
? `https://github.com/microsoft/playwright/pull/${prMatch[1]}`
: `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha} (${commitHeader})`;
const title = '[Ports]: Backport client side changes for ' + currentPlaywrightVersion;
for (const repo of ['playwright-python', 'playwright-java', 'playwright-dotnet']) {
@ -50,11 +54,11 @@ jobs:
issueBody = issueCreateData.body;
}
const newBody = issueBody.trimEnd() + `
- [ ] https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${context.sha} (${commitHeader})`;
- [ ] ${formattedCommit}`;
const data = await github.rest.issues.update({
owner: context.repo.owner,
repo: repo,
issue_number: issueNumber,
body: newBody
})
}
}

View file

@ -7,6 +7,7 @@ on:
- main
paths:
- .github/workflows/tests_bidi.yml
- packages/playwright-core/src/server/bidi/*
schedule:
# Run every day at midnight
- cron: '0 0 * * *'
@ -43,3 +44,10 @@ jobs:
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
path: test-results/report.csv
retention-days: 7

View file

@ -234,6 +234,27 @@ jobs:
env:
PWTEST_CHANNEL: chromium-tip-of-tree
chromium_tot_headless_shell:
name: Chromium tip-of-tree headless-shell-${{ matrix.os }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
browsers-to-install: chromium-tip-of-tree-headless-shell
command: npm run ctest
bot-name: "chromium-tip-of-tree-headless-shell-${{ matrix.os }}"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
PWTEST_CHANNEL: chromium-tip-of-tree-headless-shell
firefox_beta:
name: Firefox Beta ${{ matrix.os }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
@ -268,40 +289,25 @@ jobs:
- run: npx playwright install-deps
- run: utils/build/build-playwright-driver.sh
test_linux_chromium_headless_new:
name: Linux Chromium Headless New
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
browsers-to-install: chromium
command: npm run ctest
bot-name: "headless-new"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW: 1
test_linux_chromium_headless_shell:
name: Chromium Headless Shell
test_channel_chromium:
name: Test channel=chromium
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
browsers-to-install: chromium-headless-shell
# TODO: this should pass --no-shell.
# However, codegen tests do not inherit the channel and try to launch headless shell.
browsers-to-install: chromium
command: npm run ctest
bot-name: "headless-shell-${{ matrix.runs-on }}"
bot-name: "channel-chromium-${{ matrix.runs-on }}"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
PWTEST_CHANNEL: chromium-headless-shell
PWTEST_CHANNEL: chromium

View file

@ -26,6 +26,16 @@ npm run watch
npx playwright install
```
**Experimental dev mode with Hot Module Replacement for recorder/trace-viewer/UI Mode**
```
PW_HMR=1 npm run watch
PW_HMR=1 npx playwright show-trace
PW_HMR=1 npm run ctest -- --ui
PW_HMR=1 npx playwright codegen
PW_HMR=1 npx playwright show-report
```
Playwright is a multi-package repository that uses npm workspaces. For browser APIs, look at [`packages/playwright-core`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core). For test runner, see [`packages/playwright`](https://github.com/microsoft/playwright/blob/main/packages/playwright).
Note that some files are generated by the build, so the watch process might override your changes if done in the wrong file. For example, TypeScript types for the API are generated from the [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src).
@ -35,7 +45,7 @@ Coding style is fully defined in [.eslintrc](https://github.com/microsoft/playwr
npm run lint
```
Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.
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.
### Write documentation

View file

@ -1,6 +1,6 @@
# 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-131.0.6778.13-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-131.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.0-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-132.0.6834.46-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-132.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)
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
@ -8,9 +8,9 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->131.0.6778.13<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->131.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->132.0.6834.46<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->132.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.

View file

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

View file

@ -256,6 +256,13 @@ class PageHandler {
return await this._contentPage.send('disposeObject', options);
}
async ['Heap.collectGarbage']() {
Services.obs.notifyObservers(null, "child-gc-request");
Cu.forceGC();
Services.obs.notifyObservers(null, "child-cc-request");
Cu.forceCC();
}
async ['Network.getResponseBody']({requestId}) {
return this._pageNetwork.getResponseBody(requestId);
}

View file

@ -481,6 +481,17 @@ const Browser = {
},
};
const Heap = {
targets: ['page'],
types: {},
events: {},
methods: {
'collectGarbage': {
params: {},
},
},
};
const Network = {
targets: ['page'],
types: networkTypes,
@ -996,7 +1007,7 @@ const Accessibility = {
}
this.protocol = {
domains: {Browser, Page, Runtime, Network, Accessibility},
domains: {Browser, Heap, Page, Runtime, Network, Accessibility},
};
this.checkScheme = checkScheme;
this.EXPORTED_SYMBOLS = ['protocol', 'checkScheme'];

View file

@ -57,10 +57,10 @@ index 8e9bf2b413585b5a3db9370eee5d57fb6c6716ed..5a3b194b54e3813c89989f13a214c989
* Return XPCOM wrapper for the internal accessible.
*/
diff --git a/browser/app/winlauncher/LauncherProcessWin.cpp b/browser/app/winlauncher/LauncherProcessWin.cpp
index b40e0fceb567c0d217adf284e13f434e49cc8467..2c4e6d5fbf8da40954ad6a5b15e412493e43b14e 100644
index 8167d2b81c918e02ce757f7f448f22e07c29d140..3ae798880acfd8aa965ae08051f2f81855133711 100644
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -22,6 +22,7 @@
@@ -23,6 +23,7 @@
#include "mozilla/WinHeaderOnlyUtils.h"
#include "nsWindowsHelpers.h"
@ -68,7 +68,7 @@ index b40e0fceb567c0d217adf284e13f434e49cc8467..2c4e6d5fbf8da40954ad6a5b15e41249
#include <windows.h>
#include <processthreadsapi.h>
@@ -421,8 +422,18 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
@@ -422,8 +423,18 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
HANDLE stdHandles[] = {::GetStdHandle(STD_INPUT_HANDLE),
::GetStdHandle(STD_OUTPUT_HANDLE),
::GetStdHandle(STD_ERROR_HANDLE)};
@ -167,7 +167,7 @@ index d49c6fbf1bf83b832795fa674f6b41f223eef812..7ea3540947ff5f61b15f27fbf4b95564
const transportProvider = {
setListener(upgradeListener) {
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
index db5b5b990727aefcbaa47f89e0f53f4048e60038..bcd2321f46d9bca719fc530054984a2163c21f86 100644
index e1721f31d491aa8a7977eaca3d2f7f8a048546de..b3bc2d575dc3f794cbc08c603e70d34bbe69efed 100644
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -106,8 +106,15 @@ struct ParamTraits<mozilla::dom::DisplayMode>
@ -188,7 +188,7 @@ index db5b5b990727aefcbaa47f89e0f53f4048e60038..bcd2321f46d9bca719fc530054984a21
template <>
struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
@@ -2807,6 +2814,40 @@ void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
@@ -2818,6 +2825,40 @@ void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
PresContextAffectingFieldChanged();
}
@ -297,7 +297,7 @@ index 61135ab0d7894c500c3c5d80d107e283c01b6830..cc8eb043f1f78214843ec7b335dd9932
bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
index 18b2bde3da2b1e17938fddda486b1bc4ddcf0e79..793a3d002b10298f7a19a2eae4d377f6f022fd36 100644
index f0d8cb25398472d8720fcacc47081d95d3e9887c..a680d4458360c8515712ef0a986415113ae8a4e0 100644
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -324,6 +324,8 @@ void CanonicalBrowsingContext::ReplacedBy(
@ -323,7 +323,7 @@ index 18b2bde3da2b1e17938fddda486b1bc4ddcf0e79..793a3d002b10298f7a19a2eae4d377f6
}
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index 60cbd5d5b8d202fc30d5ac931ac66030bade65e7..f552a695880c5838c89ce918f61d051577cc5598 100644
index c15a424a05d23287ee21726a5fb21ff5691e4c2b..fa9989e313bbb7bf049ce1519733c4032e9f9b4b 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -15,6 +15,12 @@
@ -609,7 +609,7 @@ index 60cbd5d5b8d202fc30d5ac931ac66030bade65e7..f552a695880c5838c89ce918f61d0515
if (RefPtr<PresShell> presShell = GetPresShell()) {
presShell->ActivenessMaybeChanged();
}
@@ -6681,6 +6906,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
@@ -6688,6 +6913,10 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
return false; // no entry to save into
}
@ -620,7 +620,7 @@ index 60cbd5d5b8d202fc30d5ac931ac66030bade65e7..f552a695880c5838c89ce918f61d0515
MOZ_ASSERT(!mozilla::SessionHistoryInParent(),
"mOSHE cannot be non-null with SHIP");
nsCOMPtr<nsIDocumentViewer> viewer = mOSHE->GetDocumentViewer();
@@ -8413,6 +8642,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
@@ -8420,6 +8649,12 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
true, // aForceNoOpener
getter_AddRefs(newBC));
MOZ_ASSERT(!newBC);
@ -633,7 +633,7 @@ index 60cbd5d5b8d202fc30d5ac931ac66030bade65e7..f552a695880c5838c89ce918f61d0515
return rv;
}
@@ -9549,6 +9784,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
@@ -9556,6 +9791,16 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
nsCOMPtr<nsIRequest> req;
@ -650,7 +650,7 @@ index 60cbd5d5b8d202fc30d5ac931ac66030bade65e7..f552a695880c5838c89ce918f61d0515
rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
if (NS_SUCCEEDED(rv)) {
@@ -12747,6 +12992,9 @@ class OnLinkClickEvent : public Runnable {
@@ -12754,6 +12999,9 @@ class OnLinkClickEvent : public Runnable {
mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
mTriggeringPrincipal);
}
@ -660,7 +660,7 @@ index 60cbd5d5b8d202fc30d5ac931ac66030bade65e7..f552a695880c5838c89ce918f61d0515
return NS_OK;
}
@@ -12836,6 +13084,8 @@ nsresult nsDocShell::OnLinkClick(
@@ -12843,6 +13091,8 @@ nsresult nsDocShell::OnLinkClick(
nsCOMPtr<nsIRunnable> ev =
new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
aIsTrusted, aTriggeringPrincipal);
@ -781,10 +781,10 @@ index fdc04f16c6f547077ad8c872f9357d85d4513c50..199f8fdb0670265c715f99f5cac1a2b2
* This attempts to save any applicable layout history state (like
* scroll position) in the nsISHEntry. This is normally done
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index 235e2fcfccda18b4e923d1c1b02b5e1d9b02b089..e81abc3e18d82fa235a69911eb117ad0dcf54fd2 100644
index 79f3524037e954eb693e2882d91a7632e6e1df41..2b75a1eaff4d166f68ca4a943e10cf9c6ab28bbf 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3757,6 +3757,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
@@ -3783,6 +3783,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
}
void Document::ApplySettingsFromCSP(bool aSpeculative) {
@ -794,7 +794,7 @@ index 235e2fcfccda18b4e923d1c1b02b5e1d9b02b089..e81abc3e18d82fa235a69911eb117ad0
nsresult rv = NS_OK;
if (!aSpeculative) {
// 1) apply settings from regular CSP
@@ -3814,6 +3817,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
@@ -3840,6 +3843,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
MOZ_ASSERT(!mScriptGlobalObject,
"CSP must be initialized before mScriptGlobalObject is set!");
@ -806,7 +806,7 @@ index 235e2fcfccda18b4e923d1c1b02b5e1d9b02b089..e81abc3e18d82fa235a69911eb117ad0
// If this is a data document - no need to set CSP.
if (mLoadedAsData) {
return NS_OK;
@@ -4613,6 +4621,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
@@ -4641,6 +4649,10 @@ bool Document::HasFocus(ErrorResult& rv) const {
return false;
}
@ -817,7 +817,7 @@ index 235e2fcfccda18b4e923d1c1b02b5e1d9b02b089..e81abc3e18d82fa235a69911eb117ad0
if (!fm->IsInActiveWindow(bc)) {
return false;
}
@@ -19080,6 +19092,66 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
@@ -19139,6 +19151,66 @@ ColorScheme Document::PreferredColorScheme(IgnoreRFP aIgnoreRFP) const {
return PreferenceSheet::PrefsFor(*this).mColorScheme;
}
@ -885,10 +885,10 @@ index 235e2fcfccda18b4e923d1c1b02b5e1d9b02b089..e81abc3e18d82fa235a69911eb117ad0
if (!sLoadingForegroundTopLevelContentDocument) {
return false;
diff --git a/dom/base/Document.h b/dom/base/Document.h
index 0021e452414f9b7dc7b32a1065a82986d12dfdd7..2325b7d65bc1fb98b1dce994724c8e75c902834e 100644
index 7a8d8f2a716fc613c4095eaf1a18017887b9b924..e030e6b7ad63ad7c95227ed8f54e946190a638d8 100644
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -4053,6 +4053,9 @@ class Document : public nsINode,
@@ -4077,6 +4077,9 @@ class Document : public nsINode,
// color-scheme meta tag.
ColorScheme DefaultColorScheme() const;
@ -962,10 +962,10 @@ index 6abf6cef230c97815f17f6b7abf9f1b1de274a6f..46ead1f32e0d710b5b32e61dff72a4f7
dom::MediaCapabilities* MediaCapabilities();
dom::MediaSession* MediaSession();
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a69257799fec 100644
index 8518005d2938d35da7681c1b4230cacbe8fbaf03..e501e7e3351b6f5bdd07020dea658b9f8508b126 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8829,7 +8829,8 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8809,7 +8809,8 @@ nsresult nsContentUtils::SendMouseEvent(
bool aIgnoreRootScrollFrame, float aPressure,
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
PreventDefaultResult* aPreventDefault, bool aIsDOMEventSynthesized,
@ -975,7 +975,7 @@ index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a692
nsPoint offset;
nsCOMPtr<nsIWidget> widget = GetWidget(aPresShell, &offset);
if (!widget) return NS_ERROR_FAILURE;
@@ -8837,6 +8838,7 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8817,6 +8818,7 @@ nsresult nsContentUtils::SendMouseEvent(
EventMessage msg;
Maybe<WidgetMouseEvent::ExitFrom> exitFrom;
bool contextMenuKey = false;
@ -983,7 +983,7 @@ index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a692
if (aType.EqualsLiteral("mousedown")) {
msg = eMouseDown;
} else if (aType.EqualsLiteral("mouseup")) {
@@ -8861,6 +8863,12 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8841,6 +8843,12 @@ nsresult nsContentUtils::SendMouseEvent(
msg = eMouseHitTest;
} else if (aType.EqualsLiteral("MozMouseExploreByTouch")) {
msg = eMouseExploreByTouch;
@ -996,7 +996,7 @@ index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a692
} else {
return NS_ERROR_FAILURE;
}
@@ -8871,7 +8879,14 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8851,7 +8859,14 @@ nsresult nsContentUtils::SendMouseEvent(
Maybe<WidgetPointerEvent> pointerEvent;
Maybe<WidgetMouseEvent> mouseEvent;
@ -1012,7 +1012,7 @@ index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a692
MOZ_ASSERT(!aIsWidgetEventSynthesized,
"The event shouldn't be dispatched as a synthesized event");
if (MOZ_UNLIKELY(aIsWidgetEventSynthesized)) {
@@ -8890,8 +8905,11 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8870,8 +8885,11 @@ nsresult nsContentUtils::SendMouseEvent(
contextMenuKey ? WidgetMouseEvent::eContextMenuKey
: WidgetMouseEvent::eNormal);
}
@ -1024,7 +1024,7 @@ index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a692
mouseOrPointerEvent.pointerId = aIdentifier;
mouseOrPointerEvent.mModifiers = GetWidgetModifiers(aModifiers);
mouseOrPointerEvent.mButton = aButton;
@@ -8904,6 +8922,8 @@ nsresult nsContentUtils::SendMouseEvent(
@@ -8884,6 +8902,8 @@ nsresult nsContentUtils::SendMouseEvent(
mouseOrPointerEvent.mClickCount = aClickCount;
mouseOrPointerEvent.mFlags.mIsSynthesizedForTests = aIsDOMEventSynthesized;
mouseOrPointerEvent.mExitFrom = exitFrom;
@ -1034,10 +1034,10 @@ index 7b7deca251cf20fa4896e63e32d17303dd603263..151dd519433de858673dc1620094a692
nsPresContext* presContext = aPresShell->GetPresContext();
if (!presContext) return NS_ERROR_FAILURE;
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
index 3837cce20cfb7cc3c5a93e7b595dee632739de5c..81ccfbe139e7041eb862ab3b085f1dae76bf0a5c 100644
index b4b2244ddfbe43efa055788297a103c49989d921..2d22cdf8b25d9ce0e0daabb09f315d61a214a2be 100644
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3093,7 +3093,8 @@ class nsContentUtils {
@@ -3047,7 +3047,8 @@ class nsContentUtils {
int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure,
unsigned short aInputSourceArg, uint32_t aIdentifier, bool aToWindow,
mozilla::PreventDefaultResult* aPreventDefault,
@ -1048,10 +1048,10 @@ index 3837cce20cfb7cc3c5a93e7b595dee632739de5c..81ccfbe139e7041eb862ab3b085f1dae
static void FirePageShowEventForFrameLoaderSwap(
nsIDocShellTreeItem* aItem,
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index e2de2b30c094e30db4d33e6cf8e5fbf83f219876..f937f561c0524e04563129f2cb762ae4127e6462 100644
index c77bf80d5e1fc6db342ab47e85b7950f8a15a2d8..2f61c71cdb82b73c1de1a357315d9243a0b8c639 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -684,6 +684,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
@@ -685,6 +685,26 @@ nsDOMWindowUtils::GetPresShellId(uint32_t* aPresShellId) {
return NS_ERROR_FAILURE;
}
@ -1078,7 +1078,7 @@ index e2de2b30c094e30db4d33e6cf8e5fbf83f219876..f937f561c0524e04563129f2cb762ae4
NS_IMETHODIMP
nsDOMWindowUtils::SendMouseEvent(
const nsAString& aType, float aX, float aY, int32_t aButton,
@@ -698,7 +718,7 @@ nsDOMWindowUtils::SendMouseEvent(
@@ -699,7 +719,7 @@ nsDOMWindowUtils::SendMouseEvent(
aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, false,
aPreventDefault, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true,
aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false,
@ -1087,7 +1087,7 @@ index e2de2b30c094e30db4d33e6cf8e5fbf83f219876..f937f561c0524e04563129f2cb762ae4
}
NS_IMETHODIMP
@@ -716,7 +736,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(
@@ -717,7 +737,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(
aOptionalArgCount >= 7 ? aIdentifier : DEFAULT_MOUSE_POINTER_ID, true,
nullptr, aOptionalArgCount >= 4 ? aIsDOMEventSynthesized : true,
aOptionalArgCount >= 5 ? aIsWidgetEventSynthesized : false,
@ -1096,7 +1096,7 @@ index e2de2b30c094e30db4d33e6cf8e5fbf83f219876..f937f561c0524e04563129f2cb762ae4
}
NS_IMETHODIMP
@@ -725,13 +745,13 @@ nsDOMWindowUtils::SendMouseEventCommon(
@@ -726,13 +746,13 @@ nsDOMWindowUtils::SendMouseEventCommon(
int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
float aPressure, unsigned short aInputSourceArg, uint32_t aPointerId,
bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized,
@ -1126,10 +1126,10 @@ index 47ff326b202266b1d7d6af8bdfb72776df8a6a93..b8e084b0c788c46345b1455b8257f171
MOZ_CAN_RUN_SCRIPT
nsresult SendTouchEventCommon(
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
index 22c175c93ef7bc81640b0ad71bd6ca9c1082fea6..7d77e91afbfe7aebe0c94793c2e0606715e3acdb 100644
index cbd5cb8e4525454cac0470a14bdc63d45bf53b9a..a73297f3faafe5895453f0a6996aa30a77a97267 100644
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1684,6 +1684,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
@@ -1697,6 +1697,10 @@ Maybe<uint64_t> nsFocusManager::SetFocusInner(Element* aNewContent,
(GetActiveBrowsingContext() == newRootBrowsingContext);
}
@ -1140,7 +1140,7 @@ index 22c175c93ef7bc81640b0ad71bd6ca9c1082fea6..7d77e91afbfe7aebe0c94793c2e06067
// Exit fullscreen if a website focuses another window
if (StaticPrefs::full_screen_api_exit_on_windowRaise() &&
!isElementInActiveWindow && (aFlags & FLAG_RAISE)) {
@@ -2269,6 +2273,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
@@ -2282,6 +2286,7 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
bool aIsLeavingDocument, bool aAdjustWidget,
bool aRemainActive, Element* aElementToFocus,
uint64_t aActionId) {
@ -1148,7 +1148,7 @@ index 22c175c93ef7bc81640b0ad71bd6ca9c1082fea6..7d77e91afbfe7aebe0c94793c2e06067
LOGFOCUS(("<<Blur begin actionid: %" PRIu64 ">>", aActionId));
// hold a reference to the focused content, which may be null
@@ -2315,6 +2320,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
@@ -2328,6 +2333,11 @@ bool nsFocusManager::BlurImpl(BrowsingContext* aBrowsingContextToClear,
return true;
}
@ -1160,7 +1160,7 @@ index 22c175c93ef7bc81640b0ad71bd6ca9c1082fea6..7d77e91afbfe7aebe0c94793c2e06067
// Keep a ref to presShell since dispatching the DOM event may cause
// the document to be destroyed.
RefPtr<PresShell> presShell = docShell->GetPresShell();
@@ -2992,7 +3002,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
@@ -3005,7 +3015,9 @@ void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow,
}
}
@ -1172,10 +1172,10 @@ index 22c175c93ef7bc81640b0ad71bd6ca9c1082fea6..7d77e91afbfe7aebe0c94793c2e06067
// care of lowering the present active window. This happens in
// a separate runnable to avoid touching multiple windows in
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
index e47d4979078343102f00e93df913ff778b841804..360ab27a8f3394d18b558de80b5d0bbb963c1391 100644
index f2aa07e2c1e6df28e165b1868ad9717248360972..2b1b406c4fdf6d0716b9c29c3e640de210eae749 100644
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -2514,10 +2514,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
@@ -2516,10 +2516,16 @@ nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument,
}();
if (!isContentAboutBlankInChromeDocshell) {
@ -1196,7 +1196,7 @@ index e47d4979078343102f00e93df913ff778b841804..360ab27a8f3394d18b558de80b5d0bbb
}
}
@@ -2637,6 +2643,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
@@ -2639,6 +2645,19 @@ void nsGlobalWindowOuter::DispatchDOMWindowCreated() {
}
}
@ -1217,10 +1217,10 @@ index e47d4979078343102f00e93df913ff778b841804..360ab27a8f3394d18b558de80b5d0bbb
void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) {
diff --git a/dom/base/nsGlobalWindowOuter.h b/dom/base/nsGlobalWindowOuter.h
index 0039d6d91b23953afbd6aec2b4d1f064db3c3b1c..7a6c5da16651d34ea60c69331365d94886da1993 100644
index e2a2b560b565e6eb3cd5b4e77eb30051afe7a418..81eaca3fb0acfe90bf07acb4115a0db0786c6998 100644
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -314,6 +314,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
@@ -317,6 +317,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
// Outer windows only.
void DispatchDOMWindowCreated();
@ -1229,7 +1229,7 @@ index 0039d6d91b23953afbd6aec2b4d1f064db3c3b1c..7a6c5da16651d34ea60c69331365d948
// Outer windows only.
virtual void EnsureSizeAndPositionUpToDate() override;
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
index 4b54dcd5b4fc9c575552ae82d5ed66f313cdeb72..e75b5f148d55d8f7d7e098a84930fec0e28aa01d 100644
index 091d04dd79da3acc33aa22405ddb984ba486dfe2..40bb124fd735c2d214221c1a5fac442e26f7564e 100644
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1402,6 +1402,61 @@ void nsINode::GetBoxQuadsFromWindowOrigin(const BoxQuadOptions& aOptions,
@ -1295,10 +1295,10 @@ index 4b54dcd5b4fc9c575552ae82d5ed66f313cdeb72..e75b5f148d55d8f7d7e098a84930fec0
DOMQuad& aQuad, const GeometryNode& aFrom,
const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
index 6f980f472aefe147de47212717ca300e62e02952..3d60daf88196ed502fc647cc7b51d2eb70a281ef 100644
index 3bc7ff8a3d9e7f3148f51da13f34ea1b3ca2ba77..dcb47740ca93ab237e4f55d4225f77283f5f404b 100644
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -2303,6 +2303,10 @@ class nsINode : public mozilla::dom::EventTarget {
@@ -2317,6 +2317,10 @@ class nsINode : public mozilla::dom::EventTarget {
nsTArray<RefPtr<DOMQuad>>& aResult,
ErrorResult& aRv);
@ -1310,10 +1310,10 @@ index 6f980f472aefe147de47212717ca300e62e02952..3d60daf88196ed502fc647cc7b51d2eb
DOMQuad& aQuad, const TextOrElementOrDocument& aFrom,
const ConvertCoordinateOptions& aOptions, CallerType aCallerType,
diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp
index cf8037cd580013efe5eb578c43f45c0d21946c6a..583460796fdef633e8075013597f7c315ce4ab06 100644
index 48df3ae2d30b975269d06e6354b143abd3e5fcd8..87c8d237355668b0ff324f49be879219b1761083 100644
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -177,6 +177,11 @@ bool nsJSUtils::GetScopeChainForElement(
@@ -149,6 +149,11 @@ bool nsJSUtils::GetScopeChainForElement(
return true;
}
@ -1326,10 +1326,10 @@ index cf8037cd580013efe5eb578c43f45c0d21946c6a..583460796fdef633e8075013597f7c31
void nsJSUtils::ResetTimeZone() { JS::ResetTimeZone(); }
diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h
index cceb725d393d5e5f83c8f87491089c3fa1d57cc3..e906a7fb7c3fd72554613f640dcc272e6984d929 100644
index 8b4c1492c64884d83eb1553bc40b921e0da601b7..ee66eaa21d8e8c208204ef73fca5b3d78abefb24 100644
--- a/dom/base/nsJSUtils.h
+++ b/dom/base/nsJSUtils.h
@@ -79,6 +79,7 @@ class nsJSUtils {
@@ -71,6 +71,7 @@ class nsJSUtils {
JSContext* aCx, mozilla::dom::Element* aElement,
JS::MutableHandleVector<JSObject*> aScopeChain);
@ -1479,18 +1479,18 @@ index 7e1af00d05fbafa2d828e2c7e4dcc5c82d115f5b..e85af9718d064e4d2865bc944e9d4ba1
~Geolocation();
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index e2a77a11435a80abbb6381ffabbb5711eca0ac0d..a614efef052ca7c39457726d1f1e66f7cff777f8 100644
index d40c2a230c8c86f585935061d05e20b405c906fe..29547e7a0d75fdc8b8b30344db32287424e65fba 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -59,6 +59,7 @@
@@ -60,6 +60,7 @@
#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLDataListElement.h"
#include "mozilla/dom/HTMLOptionElement.h"
+#include "nsDocShell.h"
#include "nsIFormControlFrame.h"
#include "nsITextControlFrame.h"
#include "nsIFrame.h"
@@ -784,6 +785,13 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
#include "nsRangeFrame.h"
#include "nsError.h"
@@ -783,6 +784,13 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
return NS_ERROR_FAILURE;
}
@ -1505,7 +1505,7 @@ index e2a77a11435a80abbb6381ffabbb5711eca0ac0d..a614efef052ca7c39457726d1f1e66f7
return NS_OK;
}
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
index ac0251b4989799e9bb370a8066d10f13154bbc7c..184f4d980c35652c67da06e917e9d0b85ff34cea 100644
index 89202fa1ff22593e7cb5e20fc40b3b3b8e114449..61ed40c8454c6e85876cbc7c240496cc96f77239 100644
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -374,6 +374,26 @@ interface nsIDOMWindowUtils : nsISupports {
@ -1536,7 +1536,7 @@ index ac0251b4989799e9bb370a8066d10f13154bbc7c..184f4d980c35652c67da06e917e9d0b8
* touchstart, touchend, touchmove, and touchcancel
*
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index 204ee71ece1afa8b416caafcb4bdd242344f1a26..8597f2d0c4bd7a6fbfed9f29d002d0c59c8f8ae9 100644
index 0335a887fe157210f9eef58bf63be879f7d5de2b..dfbb8dae406f9d9276a2719f515ac5a51f8a671d 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -1656,6 +1656,21 @@ void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
@ -1822,7 +1822,7 @@ index 3b39538e51840cd9b1685b2efd2ff2e9ec83608a..c7bf4f2d53b58bbacb22b3ebebf6f3fc
return aGlobalOrNull;
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
index 4eafb2247d5aa8e989c0359d6d9d864edf73759d..e0d0b5bc78537c6fa8d0cf02cc6c772993008b91 100644
index ff2e907c0d8fc05c6e39fae612eceed405b62712..40ec25b5588a1628f9d9bd16886bed83dad49b90 100644
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -22,6 +22,7 @@
@ -1869,10 +1869,10 @@ index 2f71b284ee5f7e11f117c447834b48355784448c..2640bd57123c2b03bf4b06a2419cd020
* returned quads are further translated relative to the window
* origin -- which is not the layout origin. Further translation
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
index 6085248083194be05e85c3be7f0e69fd1928bf3d..23b72e2d0030496d5b05c88f06ed1a30ed33396b 100644
index 1ba2051ed316956a5a71f85ed5fa0735d54716e5..c0d6f45ce14040a79cfe134a4f8254434a4c53cc 100644
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -998,7 +998,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
@@ -1007,7 +1007,7 @@ void PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */) {
AssertIsOnMainThread();
nsTArray<nsString> languages;
@ -1881,7 +1881,7 @@ index 6085248083194be05e85c3be7f0e69fd1928bf3d..23b72e2d0030496d5b05c88f06ed1a30
RuntimeService* runtime = RuntimeService::GetService();
if (runtime) {
@@ -1185,8 +1185,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
@@ -1194,8 +1194,7 @@ bool RuntimeService::RegisterWorker(WorkerPrivate& aWorkerPrivate) {
}
// The navigator overridden properties should have already been read.
@ -1891,7 +1891,7 @@ index 6085248083194be05e85c3be7f0e69fd1928bf3d..23b72e2d0030496d5b05c88f06ed1a30
mNavigatorPropertiesLoaded = true;
}
@@ -1808,6 +1807,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
@@ -1817,6 +1816,13 @@ void RuntimeService::PropagateStorageAccessPermissionGranted(
}
}
@ -1905,7 +1905,7 @@ index 6085248083194be05e85c3be7f0e69fd1928bf3d..23b72e2d0030496d5b05c88f06ed1a30
template <typename Func>
void RuntimeService::BroadcastAllWorkers(const Func& aFunc) {
AssertIsOnMainThread();
@@ -2333,6 +2339,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
@@ -2342,6 +2348,14 @@ void PropagateStorageAccessPermissionGrantedToWorkers(
}
}
@ -1947,7 +1947,7 @@ index 58894a8361c7ef1dddd481ca5877a209a8b8ff5c..c481d40d79b6397b7f1d571bd9f6ae5c
bool IsWorkerGlobal(JSObject* global);
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
index 089f42307becf7c6f81199d970fb8870db494818..63fb760ac831bc88415aee1cddf8b59662e55f37 100644
index 2b48cc2980165ce51ded62faef96b93b781ede32..d8dc90983353c2f5cc1db56e327c4533d524cc1d 100644
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -700,6 +700,18 @@ class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
@ -1969,7 +1969,7 @@ index 089f42307becf7c6f81199d970fb8870db494818..63fb760ac831bc88415aee1cddf8b596
class UpdateLanguagesRunnable final : public WorkerThreadRunnable {
nsTArray<nsString> mLanguages;
@@ -2108,6 +2120,16 @@ void WorkerPrivate::UpdateContextOptions(
@@ -2113,6 +2125,16 @@ void WorkerPrivate::UpdateContextOptions(
}
}
@ -1986,7 +1986,7 @@ index 089f42307becf7c6f81199d970fb8870db494818..63fb760ac831bc88415aee1cddf8b596
void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
AssertIsOnParentThread();
@@ -5736,6 +5758,15 @@ void WorkerPrivate::UpdateContextOptionsInternal(
@@ -5740,6 +5762,15 @@ void WorkerPrivate::UpdateContextOptionsInternal(
}
}
@ -2003,7 +2003,7 @@ index 089f42307becf7c6f81199d970fb8870db494818..63fb760ac831bc88415aee1cddf8b596
const nsTArray<nsString>& aLanguages) {
WorkerGlobalScope* globalScope = GlobalScope();
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
index dfb96b7b798785d7b75c683bc0969e39487137a3..a463eec618af51fdbc25db509870598846c0fd66 100644
index da25a495a8930f7b88958553164d86fe2869416f..38f92829438ead78d73d96ee48a3fcab46c337d7 100644
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -432,6 +432,8 @@ class WorkerPrivate final
@ -2015,7 +2015,7 @@ index dfb96b7b798785d7b75c683bc0969e39487137a3..a463eec618af51fdbc25db5098705988
void UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages);
void UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key,
@@ -1059,6 +1061,8 @@ class WorkerPrivate final
@@ -1069,6 +1071,8 @@ class WorkerPrivate final
void UpdateContextOptions(const JS::ContextOptions& aContextOptions);
@ -2247,10 +2247,10 @@ index 0ec6ee3eb37c6493d8a25352fd0e54e1927bceab..885dba71bc5815e5f6f3ec2700c376aa
// No boxes to return
return;
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index f154e05a8c2e2ebf07565d087a42436feeb17f53..0af8c24c0f391c52fe2acfeb01cacb32358e6861 100644
index 2cc3c5673e246ac38cdaddb457ce36ee4c7356ce..61093cd52fc049ad77d84dd837731071fdace69d 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -11064,7 +11064,9 @@ bool PresShell::ComputeActiveness() const {
@@ -11163,7 +11163,9 @@ bool PresShell::ComputeActiveness() const {
if (!browserChild->IsVisible()) {
MOZ_LOG(gLog, LogLevel::Debug,
(" > BrowserChild %p is not visible", browserChild));
@ -2262,7 +2262,7 @@ index f154e05a8c2e2ebf07565d087a42436feeb17f53..0af8c24c0f391c52fe2acfeb01cacb32
// If the browser is visible but just due to be preserving layers
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
index 0011b1a1a36da0dec7cc6afa6fd689a4c8710d37..25bebd7b03b0b8dc595607bae07f360f3be3f284 100644
index d8995d6d94f5861d8ed839613768eb269b44e362..b946de49a0ecab449a9d1aff24c38f69a431eec0 100644
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -698,6 +698,10 @@ bool nsLayoutUtils::AllowZoomingForDocument(
@ -2276,7 +2276,7 @@ index 0011b1a1a36da0dec7cc6afa6fd689a4c8710d37..25bebd7b03b0b8dc595607bae07f360f
// True if we allow zooming for all documents on this platform, or if we are
// in RDM.
BrowsingContext* bc = aDocument->GetBrowsingContext();
@@ -9791,6 +9795,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
@@ -9768,6 +9772,9 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
/* static */
bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) {
@ -2287,10 +2287,10 @@ index 0011b1a1a36da0dec7cc6afa6fd689a4c8710d37..25bebd7b03b0b8dc595607bae07f360f
return StaticPrefs::dom_meta_viewport_enabled() || (bc && bc->InRDMPane());
}
diff --git a/layout/style/GeckoBindings.h b/layout/style/GeckoBindings.h
index d273793fc8d92b5c19ec0562730eab249cc41eb8..46b4078c6031318265a8338e01f52ab60bd9c0e8 100644
index c18d38d8ad2f80bb0d3512d1a9ae965c594bb356..22736c86eb5e3d0a44563c312e34032c157f3abe 100644
--- a/layout/style/GeckoBindings.h
+++ b/layout/style/GeckoBindings.h
@@ -596,6 +596,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*);
@@ -595,6 +595,7 @@ float Gecko_MediaFeatures_GetResolution(const mozilla::dom::Document*);
bool Gecko_MediaFeatures_PrefersReducedMotion(const mozilla::dom::Document*);
bool Gecko_MediaFeatures_PrefersReducedTransparency(
const mozilla::dom::Document*);
@ -2351,7 +2351,7 @@ index 21d5a5e1b4193d058c30268ab73c8d595436b381..11b960ec0ff3ea77857cb915d05bbdbb
+
} // namespace mozilla::net
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
index 52d867196a459578cbea1a4f626afbe51dd1abd5..2904832cbcad476fdebb54c7e24d5f14b1c0fb4b 100644
index 6ba1d8e11efbbf75f4a44d4977587429ad1371f8..d834f1f5528264f59c4547f00825e0a3b433d9dd 100644
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -413,9 +413,10 @@ class LoadInfo final : public nsILoadInfo {
@ -2360,7 +2360,7 @@ index 52d867196a459578cbea1a4f626afbe51dd1abd5..2904832cbcad476fdebb54c7e24d5f14
bool mWasSchemelessInput = false;
-
nsILoadInfo::HTTPSUpgradeTelemetryType mHttpsUpgradeTelemetry =
nsILoadInfo::NO_UPGRADE;
nsILoadInfo::NOT_INITIALIZED;
+
+ uint64_t mJugglerLoadIdentifier = 0;
};
@ -2387,10 +2387,10 @@ index 9dc2bb0da6871b905abd17d931e555429977c6c2..b71cf6393492346f16417b3ba745a235
} // namespace net
} // namespace mozilla
diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl
index 12f43b911006d5b0bbfa9936070dc0d561bc7bb4..94d20cdca548534ad5e4ef4f937e287c58768870 100644
index daccd1dc75fb1f6ba88c8f734e10c14cbdbffe8f..9621ca5dc05f12a8d81da787fa479fe03ea99e4c 100644
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -1586,4 +1586,5 @@ interface nsILoadInfo : nsISupports
@@ -1590,4 +1590,5 @@ interface nsILoadInfo : nsISupports
*/
[infallible] attribute nsILoadInfo_HTTPSUpgradeTelemetryType httpsUpgradeTelemetry;
@ -2409,7 +2409,7 @@ index 7f91d2df6f8bb4020c75c132dc8f6bf26625fa1e..ba6569f4be8fc54ec96ee44d5de45a09
/**
* Set the status and reason for the forthcoming synthesized response.
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
index 10f65a549ce886bf7f19de02714482e28a8931a5..f41d32ce90f7345ad5a9bd90e420354865f35235 100644
index ef946929c9bbd7903c8e3b32bcb373d5096aed52..a2814c5c891e2877aca9fdb4698385282b8743a9 100644
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -171,6 +171,7 @@ static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
@ -2459,10 +2459,10 @@ index e81a4538fd45c13aa60d933de5f4f32ce69fb5f2..d7945f81295c497485a09696f06ce041
if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
index f25949e6cc907ff18a76d68fc2e8005bd40146ea..9be4cb34517b06b94c6e145aef8a8ea5d2687d97 100644
index 071ed8da4135102b0b1fedce32326bdc0657c3fd..063b516001a674b558046f9191f08352eb671801 100644
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -1389,6 +1389,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
@@ -1391,6 +1391,10 @@ void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta(
void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@ -2474,10 +2474,10 @@ index f25949e6cc907ff18a76d68fc2e8005bd40146ea..9be4cb34517b06b94c6e145aef8a8ea5
nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp();
if (!preloadCsp) {
diff --git a/security/manager/ssl/nsCertOverrideService.cpp b/security/manager/ssl/nsCertOverrideService.cpp
index fcc2a45e6de8eaeb1af2404a69bd3df58cf2aec8..d4c1df007bf5993cf9e0dadbe91aa2c38afc42ec 100644
index b2e328e7c7d7a89be34b84fd176c306a3620c77c..54f24b213bcdc78c702e15d4d45a3943bc082281 100644
--- a/security/manager/ssl/nsCertOverrideService.cpp
+++ b/security/manager/ssl/nsCertOverrideService.cpp
@@ -437,7 +437,12 @@ nsCertOverrideService::HasMatchingOverride(
@@ -439,7 +439,12 @@ nsCertOverrideService::HasMatchingOverride(
bool disableAllSecurityCheck = false;
{
MutexAutoLock lock(mMutex);
@ -2491,7 +2491,7 @@ index fcc2a45e6de8eaeb1af2404a69bd3df58cf2aec8..d4c1df007bf5993cf9e0dadbe91aa2c3
}
if (disableAllSecurityCheck) {
*aIsTemporary = false;
@@ -649,14 +654,24 @@ static bool IsDebugger() {
@@ -651,14 +656,24 @@ static bool IsDebugger() {
NS_IMETHODIMP
nsCertOverrideService::
@ -2625,10 +2625,10 @@ index 00a5381133f8cec0de452c31c7151801a1acc0b9..5d3e3d6f566dc724f257beaeb994ceda
if (provider.failed) {
diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
index 144628a310662eb393d8c1a4fffbec3cf5fd1dff..69fa66f27d0533f3d90801acbfa23039ef81f7df 100644
index 6a40d032449e780bfeb77934ba141317b94e7189..1468d38355058b985f18613bd6e3bc84086fae40 100644
--- a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
+++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
@@ -632,7 +632,7 @@ void PopulateLanguages() {
@@ -553,7 +553,7 @@ void PopulateLanguages() {
// sufficient to only collect this information as the other properties are
// just reformats of Navigator::GetAcceptLanguages.
nsTArray<nsString> languages;
@ -2666,10 +2666,10 @@ index 654903fadb709be976b72f36f155e23bc0622152..815b3dc24c9fda6b1db6c4666ac68904
int32_t aMaxSelfProgress,
int32_t aCurTotalProgress,
diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
index cdba76dc8ae2206a58d7e5eb6eba97c2c3732513..266fdc6235363eafc6c7b587d5c0f597deee6e59 100644
index e3f616c4efd5d7e10ed372afa4b5c4d2d93e6a67..abb7772184c9baf23025c1577d1284b6ed1959fb 100644
--- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
@@ -1865,7 +1865,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent(
@@ -1881,7 +1881,11 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForContent(
// Open a minimal popup.
*aIsPopupRequested = true;
@ -2683,10 +2683,10 @@ index cdba76dc8ae2206a58d7e5eb6eba97c2c3732513..266fdc6235363eafc6c7b587d5c0f597
/**
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
index be01248253ee1bcc9435c3e8223ed032f498a023..0f05923c29a023511b72a81ec527300cafa17760 100644
index 6c2b400952492266a184c96b4a1ce71e87df7ffe..6e1fb4f59b6a6d70100e1eb1d15c937d8480988a 100644
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
@@ -3888,6 +3888,8 @@ export class UpdateService {
@@ -3894,6 +3894,8 @@ export class UpdateService {
}
get disabledForTesting() {
@ -2696,10 +2696,10 @@ index be01248253ee1bcc9435c3e8223ed032f498a023..0f05923c29a023511b72a81ec527300c
}
diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild
index 8c2b2bf996bd889651dc7fac1dc351b4c47b567e..07d237eb17a657ce051fd0aa5e53449c0c3159f6 100644
index f42ed17a4a75689ae9c8e769d7b6e2c3f654b8ee..5af0877335339407160dd7d10b89bd3d62adff9f 100644
--- a/toolkit/toolkit.mozbuild
+++ b/toolkit/toolkit.mozbuild
@@ -155,6 +155,7 @@ if CONFIG["ENABLE_WEBDRIVER"]:
@@ -156,6 +156,7 @@ if CONFIG["ENABLE_WEBDRIVER"]:
"/remote",
"/testing/firefox-ui",
"/testing/marionette",
@ -2760,7 +2760,7 @@ index fe72a2715da8846146377e719559c16e6ef1f7ff..a5959143bac8f62ee359fa3883a844f3
// nsDocumentViewer::LoadComplete that doesn't do various things
// that are not relevant here because this wasn't an actual
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790eafcbc6a 100644
index 139a43a1780dac34a6d8b135accac9cf39beef3f..2a855c3ae87e4e5a431cb1547cda0ee88490ca33 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -112,6 +112,7 @@
@ -2771,7 +2771,7 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
#include "mozilla/Preferences.h"
#include "mozilla/ipc/URIUtils.h"
@@ -831,6 +832,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
@@ -872,6 +873,12 @@ NS_IMETHODIMP nsExternalHelperAppService::ApplyDecodingForExtension(
return NS_OK;
}
@ -2784,7 +2784,7 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
nsresult nsExternalHelperAppService::GetFileTokenForPath(
const char16_t* aPlatformAppPath, nsIFile** aFile) {
nsDependentString platformAppPath(aPlatformAppPath);
@@ -1441,7 +1448,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
@@ -1494,7 +1501,12 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel* aChannel) {
// Strip off the ".part" from mTempLeafName
mTempLeafName.Truncate(mTempLeafName.Length() - ArrayLength(".part") + 1);
@ -2797,7 +2797,7 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
mSaver =
do_CreateInstance(NS_BACKGROUNDFILESAVERSTREAMLISTENER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1630,7 +1642,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
@@ -1683,7 +1695,36 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
return NS_OK;
}
@ -2835,7 +2835,7 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
if (NS_FAILED(rv)) {
nsresult transferError = rv;
@@ -1682,6 +1723,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
@@ -1744,6 +1785,9 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
bool alwaysAsk = true;
mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
@ -2845,7 +2845,7 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
if (alwaysAsk) {
// But we *don't* ask if this mimeInfo didn't come from
// our user configuration datastore and the user has said
@@ -2198,6 +2242,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
@@ -2260,6 +2304,16 @@ nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver* aSaver,
NotifyTransfer(aStatus);
}
@ -2862,7 +2862,7 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
return NS_OK;
}
@@ -2679,6 +2733,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
@@ -2743,6 +2797,15 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
}
}
@ -2879,10 +2879,10 @@ index ad769a235b6a7ccf7791a3d9680f3bf373b30a86..ff18e7516a2bc3258b00d958cbaa4790
// OnStartRequest)
mDialog = nullptr;
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
index 1f77e095dbfa3acc046779114007d83fc1cfa087..2354abbab7af6f6bdc3bd628722f03ea401d236a 100644
index e880b90b2df85fb3b1ab3ba8d2fc181b824e2272..dbadd74dea9b245d68da3b2856e16b7b98c655d0 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -257,6 +257,8 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
@@ -258,6 +258,8 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
mozilla::dom::BrowsingContext* aContentContext, bool aForceSave,
nsIInterfaceRequestor* aWindowContext,
nsIStreamListener** aStreamListener);
@ -2891,7 +2891,7 @@ index 1f77e095dbfa3acc046779114007d83fc1cfa087..2354abbab7af6f6bdc3bd628722f03ea
};
/**
@@ -462,6 +464,9 @@ class nsExternalAppHandler final : public nsIStreamListener,
@@ -463,6 +465,9 @@ class nsExternalAppHandler final : public nsIStreamListener,
* Upon successful return, both mTempFile and mSaver will be valid.
*/
nsresult SetUpTempFile(nsIChannel* aChannel);
@ -2902,7 +2902,7 @@ index 1f77e095dbfa3acc046779114007d83fc1cfa087..2354abbab7af6f6bdc3bd628722f03ea
* When we download a helper app, we are going to retarget all load
* notifications into our own docloader and load group instead of
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl
index 4a399acb72d4fd475c9ae43e9eadbc32f261e290..97ace81c82b16a9a993166dd4b0ddb3a721c9872 100644
index 53ea934dd4876e4b491b724385c8fbf7d00ee6cd..0b7b88c853b21ce778d8e87fea0a2bfe839ad412 100644
--- a/uriloader/exthandler/nsIExternalHelperAppService.idl
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -6,8 +6,11 @@
@ -2935,10 +2935,11 @@ index 4a399acb72d4fd475c9ae43e9eadbc32f261e290..97ace81c82b16a9a993166dd4b0ddb3a
/**
* The external helper app service is used for finding and launching
* platform specific external applications for a given mime content type.
@@ -76,6 +90,7 @@ interface nsIExternalHelperAppService : nsISupports
boolean applyDecodingForExtension(in AUTF8String aExtension,
in ACString aEncodingType);
@@ -87,6 +101,8 @@ interface nsIExternalHelperAppService : nsISupports
* `DownloadIntegration.sys.mjs`, which is implemented on all platforms.
*/
nsIFile getPreferredDownloadsDirectory();
+
+ void setDownloadInterceptor(in nsIDownloadInterceptor interceptor);
};

View file

@ -1,3 +1,3 @@
REMOTE_URL="https://github.com/WebKit/WebKit.git"
BASE_BRANCH="main"
BASE_REVISION="2ea46ab90e6511139bfb94415205038b672381e0"
BASE_REVISION="8ceb1da47e75a488ae4c12017a861636904acd4f"

View file

@ -97,7 +97,7 @@ const NSActivityOptions ActivityOptions =
for (NSString *argument in subArray) {
if (![argument hasPrefix:@"--"])
_initialURL = argument;
_initialURL = [argument copy];
if ([argument hasPrefix:@"--user-data-dir="]) {
NSRange range = NSMakeRange(16, [argument length] - 16);
_userDataDir = [[argument substringWithRange:range] copy];
@ -230,7 +230,7 @@ const NSActivityOptions ActivityOptions =
configuration = [[WKWebViewConfiguration alloc] init];
configuration.websiteDataStore = [self persistentDataStore];
configuration._controlledByAutomation = true;
configuration.preferences._fullScreenEnabled = YES;
configuration.preferences.elementFullscreenEnabled = YES;
configuration.preferences._developerExtrasEnabled = YES;
configuration.preferences._mediaDevicesEnabled = YES;
configuration.preferences._mockCaptureDevicesEnabled = YES;

File diff suppressed because it is too large Load diff

View file

@ -93,11 +93,20 @@ Element is considered stable when it has maintained the same bounding box for at
## Enabled
Element is considered enabled unless it is a `<button>`, `<select>`, `<input>` or `<textarea>` with a `disabled` property.
Element is considered enabled when it is **not disabled**.
Element is **disabled** when:
- it is a `<button>`, `<select>`, `<input>`, `<textarea>`, `<option>` or `<optgroup>` with a `[disabled]` attribute;
- it is a `<button>`, `<select>`, `<input>`, `<textarea>`, `<option>` or `<optgroup>` that is a part of a `<fieldset>` with a `[disabled]` attribute;
- it is a descendant of an element with `[aria-disabled=true]` attribute.
## Editable
Element is considered editable when it is [enabled] and does not have `readonly` property set.
Element is considered editable when it is [enabled] and is **not readonly**.
Element is **readonly** when:
- it is a `<select>`, `<input>` or `<textarea>` with a `[readonly]` attribute;
- it has an `[aria-readonly=true]` attribute and an aria role that [supports it](https://w3c.github.io/aria/#aria-readonly).
## Receives Events

View file

@ -96,7 +96,7 @@ In case this browser is connected to, clears all created contexts belonging to t
browser server.
:::note
This is similar to force quitting the browser. Therefore, you should call [`method: BrowserContext.close`] on any [BrowserContext]'s you explicitly created earlier with [`method: Browser.newContext`] **before** calling [`method: Browser.close`].
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`].
:::
The [Browser] object itself is considered to be disposed and cannot be used anymore.

View file

@ -963,9 +963,14 @@ specified.
* since: v1.8
- `permissions` <[Array]<[string]>>
A permission or an array of permissions to grant. Permissions can be one of the following values:
A list of permissions to grant.
:::danger
Supported permissions differ between browsers, and even between different versions of the same browser. Any permission may stop working after an update.
:::
Here are some permissions that may be supported by some browsers:
* `'accelerometer'`
* `'accessibility-events'`
* `'ambient-light-sensor'`
* `'background-sync'`
* `'camera'`
@ -1407,7 +1412,7 @@ This setting will change the default maximum time for all the methods accepting
* since: v1.8
- `timeout` <[float]>
Maximum time in milliseconds
Maximum time in milliseconds. Pass `0` to disable timeout.
## async method: BrowserContext.setExtraHTTPHeaders
* since: v1.8

View file

@ -161,6 +161,41 @@ 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

View file

@ -154,7 +154,8 @@ Additional locator to match.
* since: v1.49
- returns: <[string]>
Captures the aria snapshot of the given element. See [`method: LocatorAssertions.toMatchAriaSnapshot`] for the corresponding assertion.
Captures the aria snapshot of the given element.
Read more about [aria snapshots](../aria-snapshots.md) and [`method: LocatorAssertions.toMatchAriaSnapshot#2`] for the corresponding assertion.
**Usage**
@ -205,6 +206,9 @@ Below is the HTML markup and the respective ARIA snapshot:
- link "About"
```
### option: Locator.ariaSnapshot.timeout = %%-input-timeout-%%
* since: v1.49
### option: Locator.ariaSnapshot.timeout = %%-input-timeout-js-%%
* since: v1.49
@ -1479,7 +1483,7 @@ Boolean disabled = await page.GetByRole(AriaRole.Button).IsDisabledAsync();
* since: v1.14
- returns: <[boolean]>
Returns whether the element is [editable](../actionability.md#editable).
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.
:::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.

View file

@ -442,6 +442,23 @@ Expected options currently selected.
### option: LocatorAssertions.NotToHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.23
## async method: LocatorAssertions.NotToMatchAriaSnapshot
* since: v1.49
* langs: python
The opposite of [`method: LocatorAssertions.toMatchAriaSnapshot#2`].
### param: LocatorAssertions.NotToMatchAriaSnapshot.expected
* since: v1.49
- `expected` <string>
### option: LocatorAssertions.NotToMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%%
* since: v1.49
### option: LocatorAssertions.NotToMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.49
## async method: LocatorAssertions.toBeAttached
* since: v1.33
@ -701,7 +718,7 @@ expect(locator).to_be_enabled()
```csharp
var locator = Page.Locator("button.submit");
await Expect(locator).toBeEnabledAsync();
await Expect(locator).ToBeEnabledAsync();
```
### option: LocatorAssertions.toBeEnabled.enabled
@ -1181,7 +1198,7 @@ expect(locator).to_have_accessible_description("Save results to disk")
```csharp
var locator = Page.GetByTestId("save-button");
await Expect(locator).toHaveAccessibleDescriptionAsync("Save results to disk");
await Expect(locator).ToHaveAccessibleDescriptionAsync("Save results to disk");
```
### param: LocatorAssertions.toHaveAccessibleDescription.description
@ -1231,12 +1248,12 @@ expect(locator).to_have_accessible_name("Save to disk")
```csharp
var locator = Page.GetByTestId("save-button");
await Expect(locator).toHaveAccessibleNameAsync("Save to disk");
await Expect(locator).ToHaveAccessibleNameAsync("Save to disk");
```
### param: LocatorAssertions.toHaveAccessibleName.name
* since: v1.44
- `name` <[string]|[RegExp]>
- `name` <[string]|[RegExp]|[Array]<[string]|[RegExp]>>
Expected accessible name.
@ -2104,12 +2121,63 @@ Expected options currently selected.
* since: v1.23
## async method: LocatorAssertions.toMatchAriaSnapshot
## async method: LocatorAssertions.toMatchAriaSnapshot#1
* since: v1.50
* langs:
- alias-java: matchesAriaSnapshot
Asserts that the target element matches the given [accessibility snapshot](../aria-snapshots.md).
**Usage**
```js
await expect(page.locator('body')).toMatchAriaSnapshot();
await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' });
await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' });
```
```python async
await expect(page.locator('body')).to_match_aria_snapshot(path='/path/to/snapshot.yml')
```
```python sync
expect(page.locator('body')).to_match_aria_snapshot(path='/path/to/snapshot.yml')
```
```csharp
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(new { Path = "/path/to/snapshot.yml" });
```
```java
assertThat(page.locator("body")).matchesAriaSnapshot(new LocatorAssertions.MatchesAriaSnapshotOptions().setPath("/path/to/snapshot.yml"));
```
### option: LocatorAssertions.toMatchAriaSnapshot#1.name
* since: v1.50
* langs: js
- `name` <[string]>
Name of the snapshot to store in the snapshot folder corresponding to this test. Generates ordinal name if not specified.
### option: LocatorAssertions.toMatchAriaSnapshot#1.path
* since: v1.50
- `path` <[string]>
Path to the YAML snapshot file.
### option: LocatorAssertions.toMatchAriaSnapshot#1.timeout = %%-js-assertions-timeout-%%
* since: v1.50
### option: LocatorAssertions.toMatchAriaSnapshot#1.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.50
## async method: LocatorAssertions.toMatchAriaSnapshot#2
* since: v1.49
* langs:
- alias-java: matchesAriaSnapshot
Asserts that the target element matches the given accessibility snapshot.
Asserts that the target element matches the given [accessibility snapshot](../aria-snapshots.md).
**Usage**
@ -2122,7 +2190,7 @@ await expect(page.locator('body')).toMatchAriaSnapshot(`
```
```python async
await page.goto('https://demo.playwright.dev/todomvc/')
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?"
@ -2130,7 +2198,7 @@ await expect(page.locator('body')).to_match_aria_snapshot('''
```
```python sync
page.goto('https://demo.playwright.dev/todomvc/')
page.goto("https://demo.playwright.dev/todomvc/")
expect(page.locator('body')).to_match_aria_snapshot('''
- heading "todos"
- textbox "What needs to be done?"
@ -2153,9 +2221,12 @@ assertThat(page.locator("body")).matchesAriaSnapshot("""
""");
```
### param: LocatorAssertions.toMatchAriaSnapshot.expected
### param: LocatorAssertions.toMatchAriaSnapshot#2.expected
* since: v1.49
- `expected` <string>
### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%%
### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%%
* since: v1.49
### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.49

View file

@ -2762,10 +2762,6 @@ This method requires Playwright to be started in a headed mode, with a falsy [`o
Returns the PDF buffer.
:::note
Generating a pdf is currently only supported in Chromium headless.
:::
`page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call
[`method: Page.emulateMedia`] before calling `page.pdf()`:
@ -3974,7 +3970,7 @@ This setting will change the default maximum time for all the methods accepting
* since: v1.8
- `timeout` <[float]>
Maximum time in milliseconds
Maximum time in milliseconds. Pass `0` to disable timeout.
## async method: Page.setExtraHTTPHeaders
* since: v1.8

View file

@ -281,6 +281,80 @@ given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory
To specify the final trace zip file name, you need to pass `path` option to
[`method: Tracing.stopChunk`] instead.
## async method: Tracing.group
* since: v1.49
:::caution
Use `test.step` instead when available.
:::
Creates a new group within the trace, assigning any subsequent API calls to this group, until [`method: Tracing.groupEnd`] is called. Groups can be nested and will be visible in the trace viewer.
**Usage**
```js
// use test.step instead
await test.step('Log in', async () => {
// ...
});
```
```java
// All actions between group and groupEnd
// will be shown in the trace viewer as a group.
page.context().tracing().group("Open Playwright.dev > API");
page.navigate("https://playwright.dev/");
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
page.context().tracing().groupEnd();
```
```python sync
# All actions between group and group_end
# will be shown in the trace viewer as a group.
page.context.tracing.group("Open Playwright.dev > API")
page.goto("https://playwright.dev/")
page.get_by_role("link", name="API").click()
page.context.tracing.group_end()
```
```python async
# All actions between group and group_end
# will be shown in the trace viewer as a group.
await page.context.tracing.group("Open Playwright.dev > API")
await page.goto("https://playwright.dev/")
await page.get_by_role("link", name="API").click()
await page.context.tracing.group_end()
```
```csharp
// All actions between GroupAsync and GroupEndAsync
// will be shown in the trace viewer as a group.
await Page.Context.Tracing.GroupAsync("Open Playwright.dev > API");
await Page.GotoAsync("https://playwright.dev/");
await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();
await Page.Context.Tracing.GroupEndAsync();
```
### param: Tracing.group.name
* since: v1.49
- `name` <[string]>
Group name shown in the trace viewer.
### option: Tracing.group.location
* since: v1.49
- `location` ?<[Object]>
- `file` <[string]>
- `line` ?<[int]>
- `column` ?<[int]>
Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the [`method: Tracing.group`] call.
## async method: Tracing.groupEnd
* since: v1.49
Closes the last group created by [`method: Tracing.group`].
## async method: Tracing.stop
* since: v1.12

View file

@ -1001,7 +1001,11 @@ Additional arguments to pass to the browser instance. The list of Chromium flags
## browser-option-channel
- `channel` <[string]>
Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", "msedge-canary". Read more about using [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge).
Browser distribution channel.
Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-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
- `chromiumSandbox` <[boolean]>

504
docs/src/aria-snapshots.md Normal file
View file

@ -0,0 +1,504 @@
---
id: aria-snapshots
title: "Snapshot testing"
---
import LiteYouTube from '@site/src/components/LiteYouTube';
## Overview
With the Playwright 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#2`] 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 buttons 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 applications structure.
You can generate snapshots in various ways depending on your testing setup and workflow.
### 1. Generating snapshots with the Playwright code generator
If youre using Playwrights [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.
### 2. Updating snapshots with `@playwright/test` and the `--update-snapshots` flag
When using the Playwright test runner (`@playwright/test`), you can automatically update snapshots by running tests with
the `--update-snapshots` flag:
```bash
npx playwright test --update-snapshots
```
This command regenerates snapshots for assertions, including aria snapshots, replacing outdated ones. Its
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.
### 3. Using the `Locator.ariaSnapshot` method
The [`method: Locator.ariaSnapshot`] method allows you to programmatically create a YAML representation of accessible
elements within a locators 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 locators 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

@ -475,6 +475,21 @@ Setup CI/CD and run your tests frequently. The more often you run your tests the
Use Linux when running your tests on CI as it is cheaper. Developers can use whatever environment when running locally but use linux on CI. Consider setting up [Sharding](./test-sharding.md) to make CI faster.
#### Optimize browser downloads on CI
Only install the browsers that you actually need, especially on CI. For example, if you're only testing with Chromium, install just Chromium.
```bash title=".github/workflows/playwright.yml"
# Instead of installing all browsers
npx playwright install --with-deps
# Install only Chromium
npx playwright install chromium --with-deps
```
This saves both download time and disk space on your CI machines.
### Lint your tests
We recommend TypeScript and linting with ESLint for your tests to catch errors early. Use [`@typescript-eslint/no-floating-promises`](https://typescript-eslint.io/rules/no-floating-promises/) [ESLint](https://eslint.org) rule to make sure there are no missing awaits before the asynchronous calls to the Playwright API. On your CI you can run `tsc --noEmit` to ensure that functions are called with the right signature.

View file

@ -338,16 +338,101 @@ 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.
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. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for details.
#### Optimize download size on CI
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
```
#### Opt-in to 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
```
### Google Chrome & Microsoft Edge
While Playwright can download and use the recent Chromium build, it can operate against the branded Google Chrome and Microsoft Edge browsers available on the machine (note that Playwright doesn't install them by default). In particular, the current Playwright version will support Stable and Beta channels of these browsers.
Available channels are `chrome`, `msedge`, `chrome-beta`, `msedge-beta` or `msedge-dev`.
Available channels are `chrome`, `msedge`, `chrome-beta`, `msedge-beta`, `chrome-dev`, `msedge-dev`, `chrome-canary`, `msedge-canary`.
:::warning
Certain Enterprise Browser Policies may impact Playwright's ability to launch and control Google Chrome and Microsoft Edge. Running in an environment with browser policies is outside of the Playwright project's scope.
:::
:::warning
Google Chrome and Microsoft Edge have switched to a [new headless mode](https://developer.chrome.com/docs/chromium/headless) implementation that is closer to a regular headed mode. This differs from [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) that is used in Playwright by default when running headless, so expect different behavior in some cases. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) fore details.
:::
```js
import { defineConfig, devices } from '@playwright/test';
@ -401,6 +486,23 @@ pytest test_login.py --browser-channel msedge
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=msedge
```
######
* langs: python
Alternatively when using the library directly, you can specify the browser [`option: BrowserType.launch.channel`] when launching the browser:
```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# Channel can be "chrome", "msedge", "chrome-beta", "msedge-beta" or "msedge-dev".
browser = p.chromium.launch(channel="msedge")
page = browser.new_page()
page.goto("http://playwright.dev")
print(page.title())
browser.close()
```
#### Installing Google Chrome & Microsoft Edge
If Google Chrome or Microsoft Edge is not available on your machine, you can install

View file

@ -214,20 +214,15 @@ def test_popup_page(page: Page, extension_id: str) -> None:
## Headless mode
:::danger
`headless=new` mode is not officially supported by Playwright and might result in unexpected behavior.
:::
By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using the following code:
By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using [channel `chromium`](./browsers.md#opt-in-to-new-headless-mode):
```js title="fixtures.ts"
// ...
const pathToExtension = path.join(__dirname, 'my-extension');
const context = await chromium.launchPersistentContext('', {
headless: false,
channel: 'chromium',
args: [
`--headless=new`,
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
@ -239,9 +234,8 @@ const context = await chromium.launchPersistentContext('', {
path_to_extension = Path(__file__).parent.joinpath("my-extension")
context = playwright.chromium.launch_persistent_context(
"",
headless=False,
channel="chromium",
args=[
"--headless=new",
f"--disable-extensions-except={path_to_extension}",
f"--load-extension={path_to_extension}",
],

View file

@ -21,7 +21,7 @@ Playwright tests can be run on any CI provider. This guide covers one way of run
## Introduction
* langs: python, java, csharp
Playwright tests can be ran on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration.
Playwright tests can be run on any CI provider. In this section we will cover running tests on GitHub using GitHub actions. If you would like to see how to configure other CI providers check out our detailed doc on Continuous Integration.
#### You will learn
* langs: python, java, csharp

View file

@ -454,11 +454,11 @@ jobs:
### Docker
We have a [pre-built Docker image](./docker.md) which can either be used directly, or as a reference to update your existing Docker definitions.
We have a [pre-built Docker image](./docker.md) which can either be used directly or as a reference to update your existing Docker definitions.
Suggested configuration
1. Using `--ipc=host` is also recommended when using Chromium. Without it Chromium can run out of memory
and crash. Learn more about this option in [Docker docs](https://docs.docker.com/engine/reference/run/#ipc-settings---ipc).
and crash. Learn more about this option in [Docker docs](https://docs.docker.com/reference/cli/docker/container/run/#ipc).
1. Seeing other weird errors when launching Chromium? Try running your container
with `docker run --cap-add=SYS_ADMIN` when developing locally.
1. Using `--init` Docker flag or [dumb-init](https://github.com/Yelp/dumb-init) is recommended to avoid special
@ -466,7 +466,7 @@ Suggested configuration
### Azure Pipelines
For Windows or macOS agents, no additional configuration required, just install Playwright and run your tests.
For Windows or macOS agents, no additional configuration is required, just install Playwright and run your tests.
For Linux agents, you can use [our Docker container](./docker.md) with Azure
Pipelines support [running containerized

View file

@ -34,6 +34,10 @@ The recommended approach is to use `setFixedTime` to set the time to a specific
- `Event.timeStamp`
:::
:::warning
If you call `install` at any point in your test, the call _MUST_ occur before any other clock related calls (see note above for list). Calling these methods out of order will result in undefined behavior. For example, you cannot call `setInterval`, followed by `install`, then `clearInterval`, as `install` overrides the native definition of the clock functions.
:::
## Test with predefined time
Often you only need to fake `Date.now` while keeping the timers going.
@ -164,11 +168,11 @@ await Page.GotoAsync("http://localhost:3333");
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
// Assert the page state.
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM");
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
// Close the laptop lid again and open it at 10:30am.
await Page.Clock.FastForwardAsync("30:00");
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM");
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
```
## Test inactivity monitoring

View file

@ -21,7 +21,7 @@ Install the VS Code extension and generate tests directly from VS Code. The exte
### Record a New Test
To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window.
To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window.
<img width="1385" alt="record new in vs code" src="https://user-images.githubusercontent.com/13063165/220961665-615d0ab8-3f0b-439c-ad0b-0424d9aa154b.png" />
@ -58,16 +58,16 @@ In the test file in VS Code you will see your new generated actions added to you
### Generating locators
You can generate locators with the test generator.
- Click on the **Pick locator** button form the testing sidebar and then hover over elements in the browser window to see the [locator](./locators.md) highlighted underneath each element.
- Click the element you require and it will now show up in the **Pick locator** box in VS Code.
You can generate locators with the test generator.
- Click on the **Pick locator** button form the testing sidebar and then hover over elements in the browser window to see the [locator](./locators.md) highlighted underneath each element.
- Click the element you require and it will now show up in the **Pick locator** box in VS Code.
- Press <kbd>Enter</kbd> on your keyboard to copy the locator into the clipboard and then paste anywhere in your code. Or press 'escape' if you want to cancel.
<img width="1641" alt="Pick locators in VS code" src="https://user-images.githubusercontent.com/13063165/220958368-95b03620-3c9b-40a8-be74-01c96ba03cad.png" />
## Generate tests with the Playwright Inspector
When running the `codegen` command two windows will be opened, a browser window where you interact with the website you wish to test and the Playwright Inspector window where you can record your tests and then copy them into your editor.
When running the `codegen` command two windows will be opened, a browser window where you interact with the website you wish to test and the Playwright Inspector window where you can record your tests and then copy them into your editor.
### Running Codegen
@ -128,10 +128,10 @@ When you have finished interacting with the page, press the **record** button to
Use the **clear** button to clear the code to start recording again. Once finished, close the Playwright inspector window or stop the terminal command.
### Generating locators
You can generate [locators](/locators.md) with the test generator.
You can generate [locators](/locators.md) with the test generator.
* Press the `'Record'` button to stop the recording and the `'Pick Locator'` button will appear.
* Click on the `'Pick Locator'` button and then hover over elements in the browser window to see the locator highlighted underneath each element.
* Click on the `'Pick Locator'` button and then hover over elements in the browser window to see the locator highlighted underneath each element.
* To choose a locator, click on the element you would like to locate and the code for that locator will appear in the field next to the Pick Locator button.
* You can then edit the locator in this field to fine tune it or use the copy button to copy it and paste it into your code.
@ -164,19 +164,19 @@ You can use the test generator to generate tests using emulation so as to genera
Playwright opens a browser window with its viewport set to a specific width and height and is not responsive as tests need to be run under the same conditions. Use the `--viewport` option to generate tests with a different viewport size.
```bash js
npx playwright codegen --viewport-size=800,600 playwright.dev
npx playwright codegen --viewport-size="800,600" playwright.dev
```
```bash java
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --viewport-size=800,600 playwright.dev"
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --viewport-size='800,600' playwright.dev"
```
```bash python
playwright codegen --viewport-size=800,600 playwright.dev
playwright codegen --viewport-size="800,600" playwright.dev
```
```bash csharp
pwsh bin/Debug/netX/playwright.ps1 codegen --viewport-size=800,600 playwright.dev
pwsh bin/Debug/netX/playwright.ps1 codegen --viewport-size="800,600" playwright.dev
```
######
* langs: js
@ -325,7 +325,7 @@ pwsh bin/Debug/netX/playwright.ps1 codegen --timezone="Europe/Rome" --geolocatio
### Preserve authenticated state
Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests.
Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests.
```bash js
npx playwright codegen github.com/microsoft/playwright --save-storage=auth.json
@ -367,7 +367,7 @@ pwsh bin/Debug/netX/playwright.ps1 codegen github.com/microsoft/playwright --sav
#### Login
After performing authentication and closing the browser, `auth.json` will contain the storage state which you can then reuse in your tests.
After performing authentication and closing the browser, `auth.json` will contain the storage state which you can then reuse in your tests.
<img width="1394" alt="login to GitHub screen" src="https://user-images.githubusercontent.com/13063165/220561688-04b2b984-4ba6-4446-8b0a-8058876e2a02.png" />

View file

@ -103,6 +103,88 @@ Using `--ipc=host` is recommended when using Chrome ([Docker docs](https://docs.
See our [Continuous Integration guides](./ci.md) for sample configs.
### Remote Connection
You can run Playwright Server in Docker while keeping your tests running on the host system or another machine. This is useful for running tests on unsupported Linux distributions or remote execution scenarios.
#### Running the Playwright Server
Start the Playwright Server in Docker:
```bash
docker run -p 3000:3000 --rm --init -it --workdir /home/pwuser --user pwuser mcr.microsoft.com/playwright:v%%VERSION%%-noble /bin/sh -c "npx -y playwright@%%VERSION%% run-server --port 3000 --host 0.0.0.0"
```
#### Connecting to the Server
* langs: js
There are two ways to connect to the remote Playwright server:
1. Using environment variable with `@playwright/test`:
```bash
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:3000/ npx playwright test
```
2. Using the [`method: BrowserType.connect`] API for other applications:
```js
const browser = await playwright['chromium'].connect('ws://127.0.0.1:3000/');
```
#### Connecting to the Server
* langs: python, csharp, java
```python sync
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.connect("ws://127.0.0.1:3000/")
```
```python async
from playwright.async_api import async_playwright
async with async_playwright() as p:
browser = await p.chromium.connect("ws://127.0.0.1:3000/")
```
```csharp
using Microsoft.Playwright;
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.ConnectAsync("ws://127.0.0.1:3000/");
```
```java
package org.example;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connect("ws://127.0.0.1:3000/");
}
}
}
```
#### Network Configuration
If you need to access local servers from within the Docker container:
```bash
docker run --add-host=hostmachine:host-gateway -p 3000:3000 --rm --init -it --workdir /home/pwuser --user pwuser mcr.microsoft.com/playwright:v%%VERSION%%-noble /bin/sh -c "npx -y playwright@%%VERSION%% run-server --port 3000 --host 0.0.0.0"
```
This makes `hostmachine` point to the host's localhost. Your tests should use `hostmachine` instead of `localhost` when accessing local servers.
:::note
When running tests remotely, ensure the Playwright version in your tests matches the version running in the Docker container.
:::
## Image tags
See [all available image tags].
@ -132,7 +214,7 @@ Browser builds for Firefox and WebKit are built for the [glibc](https://en.wikip
You can use the [.NET install script](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script) in order to install different SDK versions:
```bash
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir /usr/share/dotnet --channel 6.0
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir /usr/share/dotnet --channel 9.0
```
## Build your own image

View file

@ -7,7 +7,7 @@ title: "Installation"
Playwright was created specifically to accommodate the needs of end-to-end testing. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with native mobile emulation.
You can choose to use [MSTest base classes](./test-runners.md#mstest) or [NUnit base classes](./test-runners.md#nunit) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure.
You can choose to use MSTest, NUnit, or xUnit [base classes](./test-runners.md) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure.
1. Start by creating a new project with `dotnet new`. This will create the `PlaywrightTests` directory which includes a `UnitTest1.cs` file:
@ -17,6 +17,7 @@ You can choose to use [MSTest base classes](./test-runners.md#mstest) or [NUnit
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -34,6 +35,14 @@ dotnet new mstest -n PlaywrightTests
cd PlaywrightTests
```
</TabItem>
<TabItem value="xunit">
```bash
dotnet new xunit -n PlaywrightTests
cd PlaywrightTests
```
</TabItem>
</Tabs>
@ -45,6 +54,7 @@ cd PlaywrightTests
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -60,6 +70,13 @@ dotnet add package Microsoft.Playwright.NUnit
dotnet add package Microsoft.Playwright.MSTest
```
</TabItem>
<TabItem value="xunit">
```bash
dotnet add package Microsoft.Playwright.Xunit
```
</TabItem>
</Tabs>
@ -87,6 +104,7 @@ Edit the `UnitTest1.cs` file with the code below to create an example end-to-end
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -164,6 +182,41 @@ public class ExampleTest : PageTest
```
</TabItem>
<TabItem value="xunit">
```csharp title="UnitTest1.cs"
using System.Text.RegularExpressions;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
namespace PlaywrightTests;
public class UnitTest1: PageTest
{
[Fact]
public async Task HasTitle()
{
await Page.GotoAsync("https://playwright.dev");
// Expect a title "to contain" a substring.
await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
}
[Fact]
public async Task GetStartedLink()
{
await Page.GotoAsync("https://playwright.dev");
// Click the get started link.
await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync();
// Expects page to have a heading with the name of Installation.
await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync();
}
}
```
</TabItem>
</Tabs>
## Running the Example Tests
@ -190,4 +243,4 @@ See our doc on [Running and Debugging Tests](./running-tests.md) to learn more a
- [Generate tests with Codegen](./codegen-intro.md)
- [See a trace of your tests](./trace-viewer-intro.md)
- [Run tests on CI](./ci-intro.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
- [Learn more about the MSTest, NUnit, and xUnit base classes](./test-runners.md)

View file

@ -80,7 +80,7 @@ The `tests` folder contains a basic example test to help you get started with te
## Running the Example Test
By default tests will be run on all 3 browsers, chromium, firefox and webkit using 3 workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
By default tests will be run on all 3 browsers, Chromium, Firefox and WebKit using 3 workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
<Tabs
defaultValue="npm"

View file

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

View file

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

View file

@ -115,8 +115,7 @@ await page.GotoAsync("https://example.com");
You can configure pages to load over the HTTP(S) proxy or SOCKSv5. Proxy can be either set globally
for the entire browser, or for each browser context individually.
You can optionally specify username and password for HTTP(S) proxy, you can also specify hosts to
bypass proxy for.
You can optionally specify username and password for HTTP(S) proxy, you can also specify hosts to bypass the [`option: Browser.newContext.proxy`] for.
Here is an example of a global proxy:

View file

@ -5,6 +5,91 @@ toc_max_heading_level: 2
---
## Version 1.49
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```csharp
await page.GotoAsync("https://playwright.dev");
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"
- banner:
- heading /Playwright enables reliable/ [level=1]
- link ""Get started""
- link ""Star microsoft/playwright on GitHub""
- main:
- img ""Browsers (Chromium, Firefox, WebKit)""
- heading ""Any browser • Any platform • One API""
");
```
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
Learn more in the [aria snapshots guide](./aria-snapshots).
### Tracing groups
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
```csharp
// All actions between GroupAsync and GroupEndAsync
// will be shown in the trace viewer as a group.
await Page.Context.Tracing.GroupAsync("Open Playwright.dev > API");
await Page.GotoAsync("https://playwright.dev/");
await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();
await Page.Context.Tracing.GroupEndAsync();
```
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```xml csharp title="runsettings.xml"
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<Playwright>
<BrowserName>chromium</BrowserName>
<LaunchOptions>
<Channel>chromium</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
```bash csharp
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=chromium
```
### Miscellaneous
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- `<canvas>` elements inside a snapshot now draw a preview.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
### WebSocket routing
@ -788,7 +873,7 @@ All the same methods are also available on [Locator], [FrameLocator] and [Frame]
- [`method: LocatorAssertions.toHaveAttribute`] with an empty value does not match missing attribute anymore. For example, the following snippet will succeed when `button` **does not** have a `disabled` attribute.
```csharp
await Expect(Page.GetByRole(AriaRole.Button)).ToHaveAttribute("disabled", "");
await Expect(Page.GetByRole(AriaRole.Button)).ToHaveAttributeAsync("disabled", "");
```
### Browser Versions

View file

@ -4,6 +4,79 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.49
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```java
page.navigate("https://playwright.dev");
assertThat(page.locator("body")).matchesAriaSnapshot("""
- banner:
- heading /Playwright enables reliable/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- main:
- img "Browsers (Chromium, Firefox, WebKit)"
- heading "Any browser • Any platform • One API"
""");
```
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
Learn more in the [aria snapshots guide](./aria-snapshots).
### Tracing groups
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
```java
// All actions between group and groupEnd
// will be shown in the trace viewer as a group.
page.context().tracing().group("Open Playwright.dev > API");
page.navigate("https://playwright.dev/");
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
page.context().tracing().groupEnd();
```
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```java
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setChannel("chromium"));
```
### Miscellaneous
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- `<canvas>` elements inside a snapshot now draw a preview.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
### WebSocket routing

View file

@ -6,6 +6,98 @@ toc_max_heading_level: 2
import LiteYouTube from '@site/src/components/LiteYouTube';
## Version 1.49
<LiteYouTube
id="S5wCft-ImKk"
title="Playwright 1.49"
/>
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```js
await page.goto('https://playwright.dev');
await expect(page.locator('body')).toMatchAriaSnapshot(`
- banner:
- heading /Playwright enables reliable/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- main:
- img "Browsers (Chromium, Firefox, WebKit)"
- heading "Any browser • Any platform • One API"
`);
```
You can generate this assertion with [Test Generator](./codegen) and update the expected snapshot with `--update-snapshots` command line flag.
Learn more in the [aria snapshots guide](./aria-snapshots).
### Test runner
- New option [`property: TestConfig.tsconfig`] allows to specify a single `tsconfig` to be used for all tests.
- New method [`method: Test.fail.only`] to focus on a failing test.
- Options [`property: TestConfig.globalSetup`] and [`property: TestConfig.globalTeardown`] now support multiple setups/teardowns.
- New value `'on-first-failure'` for [`property: TestOptions.screenshot`].
- Added "previous" and "next" buttons to the HTML report to quickly switch between test cases.
- New properties [`property: TestInfoError.cause`] and [`property: TestError.cause`] mirroring [`Error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause).
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
#### What do I need to do?
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Other breaking changes
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- Package `@playwright/experimental-ct-vue2` will no longer be updated.
- Package `@playwright/experimental-ct-solid` will no longer be updated.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], channel: 'chromium' },
},
],
});
```
### Miscellaneous
- `<canvas>` elements inside a snapshot now draw a preview.
- New method [`method: Tracing.group`] to visually group actions in the trace.
- Playwright docker images switched from Node.js v20 to Node.js v22 LTS.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
<LiteYouTube

View file

@ -4,6 +4,80 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.49
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```python
page.goto("https://playwright.dev")
expect(page.locator('body')).to_match_aria_snapshot('''
- banner:
- heading /Playwright enables reliable/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- main:
- img "Browsers (Chromium, Firefox, WebKit)"
- heading "Any browser • Any platform • One API"
''')
```
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
Learn more in the [aria snapshots guide](./aria-snapshots).
### Tracing groups
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
```python
# All actions between group and group_end
# will be shown in the trace viewer as a group.
page.context.tracing.group("Open Playwright.dev > API")
page.goto("https://playwright.dev/")
page.get_by_role("link", name="API").click()
page.context.tracing.group_end()
```
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```bash python
pytest test_login.py --browser-channel chromium
```
### Miscellaneous
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- `<canvas>` elements inside a snapshot now draw a preview.
- Python 3.8 is not supported anymore.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
### WebSocket routing

View file

@ -113,6 +113,7 @@ dotnet test --filter "Name~GetStartedLink"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -128,6 +129,19 @@ dotnet test -- NUnit.NumberOfTestWorkers=5
dotnet test -- MSTest.Parallelize.Workers=5
```
</TabItem>
<TabItem value="xunit">
```bash
dotnet test -- xUnit.MaxParallelThreads=5
```
See [here](https://xunit.net/docs/running-tests-in-parallel.html) for more information to run tests in parallel with xUnit.
:::note
We recommend xUnit 2.8+ which uses the [`conservative` parallelism algorithm](https://xunit.net/docs/running-tests-in-parallel.html#algorithms) by default.
:::
</TabItem>
</Tabs>

View file

@ -33,7 +33,7 @@ npx playwright test --ui
![UI Mode](https://github.com/microsoft/playwright/assets/13063165/c5b501cc-4f5d-485a-87cc-66044c651786)
Check out or [detailed guide on UI Mode](./test-ui-mode.md) to learn more about it's features.
Check out or [detailed guide on UI Mode](./test-ui-mode.md) to learn more about its features.
### Run tests in headed mode
@ -112,11 +112,11 @@ npx playwright test --ui
![showing errors in ui mode](https://github.com/microsoft/playwright/assets/13063165/ffca2fd1-5349-41fb-ade9-ace143bb2c58)
While debugging you can use the Pick Locator button to select an element on the page and see the locator that Playwright would use to find that element. You can also edit the locator in the locator playground and see it highlighting live on the Browser window. Use the Copy Locator button to copy the locator to your clipboard and then paste it into you test.
While debugging you can use the Pick Locator button to select an element on the page and see the locator that Playwright would use to find that element. You can also edit the locator in the locator playground and see it highlighting live on the Browser window. Use the Copy Locator button to copy the locator to your clipboard and then paste it into your test.
![pick locator in ui mode](https://github.com/microsoft/playwright/assets/13063165/9e7eeb84-bd26-4010-8614-75e24b56c716)
Check out our [detailed guide on UI Mode](./test-ui-mode.md) to learn more about it's features.
Check out our [detailed guide on UI Mode](./test-ui-mode.md) to learn more about its features.
### Debug tests with the Playwright Inspector

View file

@ -114,10 +114,16 @@ See [`property: TestConfig.shard`].
## property: FullConfig.updateSnapshots
* since: v1.10
- type: <[UpdateSnapshots]<"all"|"none"|"missing">>
- type: <[UpdateSnapshots]<"all"|"changed"|"missing"|"none">>
See [`property: TestConfig.updateSnapshots`].
## property: FullConfig.updateSourceMethod
* since: v1.50
- type: <[UpdateSourceMethod]<"overwrite"|"3way"|"patch">>
See [`property: TestConfig.updateSourceMethod`].
## property: FullConfig.version
* since: v1.20
- type: <[string]>

View file

@ -1767,6 +1767,118 @@ Whether to box the step in the report. Defaults to `false`. When the step is box
Specifies a custom location for the step to be shown in test reports and trace viewer. By default, location of the [`method: Test.step`] call is shown.
### option: Test.step.timeout
* since: v1.50
- `timeout` <[float]>
Maximum time in milliseconds for the step to finish. Defaults to `0` (no timeout).
## async method: Test.step.fail
* since: v1.50
- returns: <[void]>
Marks a test step as "should fail". Playwright runs this test step and ensures that it actually fails. This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed.
:::note
If the step exceeds the timeout, a [TimeoutError] is thrown. This indicates the step did not fail as expected.
:::
**Usage**
You can declare a test step as failing, so that Playwright ensures it actually fails.
```js
import { test, expect } from '@playwright/test';
test('my test', async ({ page }) => {
// ...
await test.step.fail('currently failing', async () => {
// ...
});
});
```
### param: Test.step.fail.title
* since: v1.50
- `title` <[string]>
Step name.
### param: Test.step.fail.body
* since: v1.50
- `body` <[function]\(\):[Promise]<[any]>>
Step body.
### option: Test.step.fail.box
* since: v1.50
- `box` <boolean>
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
### option: Test.step.fail.location
* since: v1.50
- `location` <[Location]>
Specifies a custom location for the step to be shown in test reports and trace viewer. By default, location of the [`method: Test.step`] call is shown.
### option: Test.step.fail.timeout
* since: v1.50
- `timeout` <[float]>
Maximum time in milliseconds for the step to finish. Defaults to `0` (no timeout).
## async method: Test.step.fixme
* since: v1.50
- returns: <[void]>
Mark a test step as "fixme", with the intention to fix it. Playwright will not run the step.
**Usage**
You can declare a test step as failing, so that Playwright ensures it actually fails.
```js
import { test, expect } from '@playwright/test';
test('my test', async ({ page }) => {
// ...
await test.step.fixme('not yet ready', async () => {
// ...
});
});
```
### param: Test.step.fixme.title
* since: v1.50
- `title` <[string]>
Step name.
### param: Test.step.fixme.body
* since: v1.50
- `body` <[function]\(\):[Promise]<[any]>>
Step body.
### option: Test.step.fixme.box
* since: v1.50
- `box` <boolean>
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
### option: Test.step.fixme.location
* since: v1.50
- `location` <[Location]>
Specifies a custom location for the step to be shown in test reports and trace viewer. By default, location of the [`method: Test.step`] call is shown.
### option: Test.step.fixme.timeout
* since: v1.50
- `timeout` <[float]>
Maximum time in milliseconds for the step to finish. Defaults to `0` (no timeout).
## method: Test.use
* since: v1.10

View file

@ -570,12 +570,13 @@ export default defineConfig({
## property: TestConfig.updateSnapshots
* since: v1.10
- type: ?<[UpdateSnapshots]<"all"|"none"|"missing">>
- type: ?<[UpdateSnapshots]<"all"|"changed"|"missing"|"none">>
Whether to update expected snapshots with the actual results produced by the test run. Defaults to `'missing'`.
* `'all'` - All tests that are executed will update snapshots that did not match. Matching snapshots will not be updated.
* `'none'` - No snapshots are updated.
* `'all'` - All tests that are executed will update snapshots.
* `'changed'` - All tests that are executed will update snapshots that did not match. Matching snapshots will not be updated.
* `'missing'` - Missing snapshots are created, for example when authoring a new test and running it for the first time. This is the default.
* `'none'` - No snapshots are updated.
Learn more about [snapshots](../test-snapshots.md).
@ -589,6 +590,15 @@ export default defineConfig({
});
```
## property: TestConfig.updateSourceMethod
* since: v1.50
- type: ?<[UpdateSourceMethod]<"overwrite"|"3way"|"patch">>
Defines how to update the source code snapshots.
* `'overwrite'` - Overwrite the source code snapshot with the actual result.
* `'3way'` - Use a three-way merge to update the source code snapshot.
* `'patch'` - Use a patch to update the source code snapshot. This is the default.
## property: TestConfig.use
* since: v1.10
- type: ?<[TestOptions]>

View file

@ -392,8 +392,11 @@ export default defineConfig({
});
```
## property: TestOptions.locale = %%-context-option-locale-%%
## property: TestOptions.locale
* since: v1.10
- type: <[string]>
Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. Defaults to `en-US`. Learn more about emulation in our [emulation guide](../emulation.md#locale--timezone).
**Usage**

View file

@ -81,6 +81,7 @@ expect.set_options(timeout=10_000)
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -127,6 +128,24 @@ public class UnitTest1 : PageTest
}
```
</TabItem>
<TabItem value="xunit">
```csharp title="UnitTest1.cs"
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
namespace PlaywrightTests;
public class UnitTest1: PageTest
{
UnitTest1()
{
SetDefaultExpectTimeout(10_000);
}
// ...
}
```
</TabItem>
</Tabs>

View file

@ -254,7 +254,7 @@ Note that by default `toPass` has timeout 0 and does not respect custom [expect
You can extend Playwright assertions by providing custom matchers. These matchers will be available on the `expect` object.
In this example we add a custom `toHaveAmount` function. Custom matcher should return a `message` callback and a `pass` flag indicating whether the assertion passed.
In this example we add a custom `toHaveAmount` function. Custom matcher should return a `pass` flag indicating whether the assertion passed, and a `message` callback that's used when the assertion fails.
```js title="fixtures.ts"
import { expect as baseExpect } from '@playwright/test';
@ -279,7 +279,7 @@ export const expect = baseExpect.extend({
? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
'\n\n' +
`Locator: ${locator}\n` +
`Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` +
`Expected: not ${this.utils.printExpected(expected)}\n` +
(matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '')
: () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
'\n\n' +

View file

@ -76,33 +76,40 @@ Here are the most common options available in the command line.
Complete set of Playwright Test options is available in the [configuration file](./test-use-options.md). Following options can be passed to a command line and take priority over the configuration file:
<!-- // Note: packages/playwright/src/program.ts is the source of truth. -->
| Option | Description |
| :- | :- |
| Non-option arguments | Each argument is treated as a regular expression matched against the full test file path. Only tests from the files matching the pattern will be executed. Special symbols like `$` or `*` should be escaped with `\`. In many shells/terminals you may need to quote the arguments. |
| `-c <file>` or `--config <file>`| Configuration file. If not passed, defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. |
| `--debug`| Run tests with Playwright Inspector. Shortcut for `PWDEBUG=1` environment variable and `--timeout=0 --max-failures=1 --headed --workers=1` options.|
| `--fail-on-flaky-tests` | Fails test runs that contain flaky tests. By default flaky tests count as successes. |
| `--forbid-only` | Whether to disallow `test.only`. Useful on CI.|
| `--global-timeout <number>` | Total timeout for the whole test run in milliseconds. By default, there is no global timeout. Learn more about [various timeouts](./test-timeouts.md).|
| `-g <grep>` or `--grep <grep>` | Only run tests matching this regular expression. For example, this will run `'should add to cart'` when passed `-g "add to cart"`. The regular expression will be tested against the string that consists of the project name, test file name, `test.describe` titles if any, test title and all test tags, separated by spaces, e.g. `chromium my-test.spec.ts my-suite my-test @smoke`. The filter does not apply to the tests from dependency projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies). |
| `--grep-invert <grep>` | Only run tests **not** matching this regular expression. The opposite of `--grep`. The filter does not apply to the tests from dependency projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies).|
| `--headed` | Run tests in headed browsers. Useful for debugging. |
| `--ignore-snapshots` | Whether to ignore [snapshots](./test-snapshots.md). Use this when snapshot expectations are known to be different, e.g. running tests on Linux against Windows screenshots. |
| `--last-failed` | Only re-run the failures.|
| `--list` | list all the tests, but do not run them.|
| `--max-failures <N>` or `-x`| Stop after the first `N` test failures. Passing `-x` stops after the first failure.|
| `--no-deps` | Ignore the dependencies between projects and behave as if they were not specified. |
| `--output <dir>` | Directory for artifacts produced by tests, defaults to `test-results`. |
| `--only-changed [ref]` | Only run test files that have been changed between working tree and "ref". Defaults to running all uncommitted changes with ref=HEAD. Only supports Git. |
| `--pass-with-no-tests` | Allows the test suite to pass when no files are found. |
| `--project <name>` | Only run tests from the specified [projects](./test-projects.md), supports '*' wildcard. Defaults to running all projects defined in the configuration file.|
| `--quiet` | Whether to suppress stdout and stderr from the tests. |
| `--repeat-each <N>` | Run each test `N` times, defaults to one. |
| `--reporter <reporter>` | Choose a reporter: minimalist `dot`, concise `line` or detailed `list`. See [reporters](./test-reporters.md) for more information. You can also pass a path to a [custom reporter](./test-reporters.md#custom-reporters) file. |
| `--retries <number>` | The maximum number of [retries](./test-retries.md#retries) for flaky tests, defaults to zero (no retries). |
| `--shard <shard>` | [Shard](./test-parallel.md#shard-tests-between-multiple-machines) tests and execute only selected shard, specified in the form `current/all`, 1-based, for example `3/5`.|
| `--timeout <number>` | Maximum timeout in milliseconds for each test, defaults to 30 seconds. Learn more about [various timeouts](./test-timeouts.md).|
| `--trace <mode>` | Force tracing mode, can be `on`, `off`, `on-first-retry`, `on-all-retries`, `retain-on-failure` |
| `--tsconfig <path>` | Path to a single tsconfig applicable to all imported files. See [tsconfig resolution](./test-typescript.md#tsconfig-resolution) for more details. |
| `--update-snapshots` or `-u` | Whether to update [snapshots](./test-snapshots.md) with actual results instead of comparing them. Use this when snapshot expectations have changed.|
| `--workers <number>` or `-j <number>`| The maximum number of concurrent worker processes that run in [parallel](./test-parallel.md). |
| Non-option arguments | Each argument is treated as a regular expression matched against the full test file path. Only tests from files matching the pattern will be executed. Special symbols like `$` or `*` should be escaped with `\`. In many shells/terminals you may need to quote the arguments. |
| `-c <file>` or `--config <file>` | Configuration file, or a test directory with optional "playwright.config.&#123;m,c&#125;?&#123;js,ts&#125;". Defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. |
| `--debug` | Run tests with Playwright Inspector. Shortcut for `PWDEBUG=1` environment variable and `--timeout=0 --max-failures=1 --headed --workers=1` options. |
| `--fail-on-flaky-tests` | Fail if any test is flagged as flaky (default: false). |
| `--forbid-only` | Fail if `test.only` is called (default: false). Useful on CI. |
| `--fully-parallel` | Run all tests in parallel (default: false). |
| `--global-timeout <timeout>` | Maximum time this test suite can run in milliseconds (default: unlimited). |
| `-g <grep>` or `--grep <grep>` | Only run tests matching this regular expression (default: ".*"). |
| `-gv <grep>` or `--grep-invert <grep>` | Only run tests that do not match this regular expression. |
| `--headed` | Run tests in headed browsers (default: headless). |
| `--ignore-snapshots` | Ignore screenshot and snapshot expectations. |
| `--last-failed` | Only re-run the failures. |
| `--list` | Collect all the tests and report them, but do not run. |
| `--max-failures <N>` or `-x` | Stop after the first `N` failures. Passing `-x` stops after the first failure. |
| `--no-deps` | Do not run project dependencies. |
| `--output <dir>` | Folder for output artifacts (default: "test-results"). |
| `--only-changed [ref]` | Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git. |
| `--pass-with-no-tests` | Makes test run succeed even if no tests were found. |
| `--project <project-name...>` | Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects). |
| `--quiet` | Suppress stdio. |
| `--repeat-each <N>` | Run each test `N` times (default: 1). |
| `--reporter <reporter>` | Reporter to use, comma-separated, can be "dot", "line", "list", or others (default: "list"). You can also pass a path to a custom reporter file. |
| `--retries <retries>` | Maximum retry count for flaky tests, zero for no retries (default: no retries). |
| `--shard <shard>` | Shard tests and execute only the selected shard, specified in the form "current/all", 1-based, e.g., "3/5". |
| `--timeout <timeout>` | Specify test timeout threshold in milliseconds, zero for unlimited (default: 30 seconds). |
| `--trace <mode>` | Force tracing mode, can be "on", "off", "on-first-retry", "on-all-retries", "retain-on-failure", "retain-on-first-failure". |
| `--tsconfig <path>` | Path to a single tsconfig applicable to all imported files (default: look up tsconfig for each imported file separately). |
| `--ui` | Run tests in interactive UI mode. |
| `--ui-host <host>` | Host to serve UI on; specifying this option opens UI in a browser tab. |
| `--ui-port <port>` | Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab. |
| `-u` or `--update-snapshots [mode]` | Update snapshots with actual results. Possible values are "all", "changed", "missing", and "none". Not passing defaults to "missing"; passing without a value defaults to "changed". |
| `-j <workers>` or `--workers <workers>` | Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%). |
| `-x` | Stop after the first failure. |

View file

@ -40,7 +40,7 @@ test('event should work', async ({ mount }) => {
## How to get started
Adding Playwright Test to an existing project is easy. Below are the steps to enable Playwright Test for a React, Vue, Svelte or Solid project.
Adding Playwright Test to an existing project is easy. Below are the steps to enable Playwright Test for a React, Vue or Svelte project.
### Step 1: Install Playwright Test for components for your respective framework
@ -106,7 +106,6 @@ component is mounted using this script. It can be either a `.js`, `.ts`, `.jsx`
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -168,20 +167,6 @@ test('should work', async ({ mount }) => {
</TabItem>
<TabItem value="solid">
```js title="app.spec.tsx"
import { test, expect } from '@playwright/experimental-ct-solid';
import App from './App';
test('should work', async ({ mount }) => {
const component = await mount(<App />);
await expect(component).toContainText('Learn Solid');
});
```
</TabItem>
</Tabs>
### Step 3. Run the tests
@ -309,7 +294,6 @@ Provide props to a component when mounted.
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -325,17 +309,6 @@ test('props', async ({ mount }) => {
});
```
</TabItem>
<TabItem value="solid">
```js title="component.spec.tsx"
import { test } from '@playwright/experimental-ct-solid';
test('props', async ({ mount }) => {
const component = await mount(<Component msg="greetings" />);
});
```
</TabItem>
<TabItem value="svelte">
@ -379,7 +352,6 @@ Provide callbacks/events to a component when mounted.
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -395,17 +367,6 @@ test('callback', async ({ mount }) => {
});
```
</TabItem>
<TabItem value="solid">
```js title="component.spec.tsx"
import { test } from '@playwright/experimental-ct-solid';
test('callback', async ({ mount }) => {
const component = await mount(<Component onClick={() => {}} />);
});
```
</TabItem>
<TabItem value="svelte">
@ -449,7 +410,6 @@ Provide children/slots to a component when mounted.
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -465,17 +425,6 @@ test('children', async ({ mount }) => {
});
```
</TabItem>
<TabItem value="solid">
```js title="component.spec.tsx"
import { test } from '@playwright/experimental-ct-solid';
test('children', async ({ mount }) => {
const component = await mount(<Component>Child</Component>);
});
```
</TabItem>
<TabItem value="svelte">
@ -519,7 +468,6 @@ You can use `beforeMount` and `afterMount` hooks to configure your app. This let
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Vue3', value: 'vue3'},
]
}>
@ -554,37 +502,6 @@ You can use `beforeMount` and `afterMount` hooks to configure your app. This let
</TabItem>
<TabItem value="solid">
```js title="playwright/index.tsx"
import { beforeMount, afterMount } from '@playwright/experimental-ct-solid/hooks';
import { Router } from '@solidjs/router';
export type HooksConfig = {
enableRouting?: boolean;
}
beforeMount<HooksConfig>(async ({ App, hooksConfig }) => {
if (hooksConfig?.enableRouting)
return <Router><App /></Router>;
});
```
```js title="src/pages/ProductsPage.spec.tsx"
import { test, expect } from '@playwright/experimental-ct-solid';
import type { HooksConfig } from '../playwright';
import { ProductsPage } from './pages/ProductsPage';
test('configure routing through hooks config', async ({ page, mount }) => {
const component = await mount<HooksConfig>(<ProductsPage />, {
hooksConfig: { enableRouting: true },
});
await expect(component.getByRole('link')).toHaveAttribute('href', '/products/42');
});
```
</TabItem>
<TabItem value="vue3">
```js title="playwright/index.ts"
@ -626,7 +543,6 @@ Unmount the mounted component from the DOM. This is useful for testing the compo
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -643,18 +559,6 @@ test('unmount', async ({ mount }) => {
});
```
</TabItem>
<TabItem value="solid">
```js title="component.spec.tsx"
import { test } from '@playwright/experimental-ct-solid';
test('unmount', async ({ mount }) => {
const component = await mount(<Component/>);
await component.unmount();
});
```
</TabItem>
<TabItem value="svelte">
@ -700,7 +604,6 @@ Update props, slots/children, and/or events/callbacks of a mounted component. Th
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -719,20 +622,6 @@ test('update', async ({ mount }) => {
});
```
</TabItem>
<TabItem value="solid">
```js title="component.spec.tsx"
import { test } from '@playwright/experimental-ct-solid';
test('update', async ({ mount }) => {
const component = await mount(<Component/>);
await component.update(
<Component msg="greetings" onClick={() => {}}>Child</Component>
);
});
```
</TabItem>
<TabItem value="svelte">
@ -820,7 +709,7 @@ test('example test', async ({ mount, router }) => {
## Frequently asked questions
### What's the difference between `@playwright/test` and `@playwright/experimental-ct-{react,svelte,vue,solid}`?
### What's the difference between `@playwright/test` and `@playwright/experimental-ct-{react,svelte,vue}`?
```js
test('…', async ({ mount, page, context }) => {
@ -828,13 +717,12 @@ test('…', async ({ mount, page, context }) => {
});
```
`@playwright/experimental-ct-{react,svelte,vue,solid}` wrap `@playwright/test` to provide an additional built-in component-testing specific fixture called `mount`:
`@playwright/experimental-ct-{react,svelte,vue}` wrap `@playwright/test` to provide an additional built-in component-testing specific fixture called `mount`:
<Tabs
defaultValue="react"
values={[
{label: 'React', value: 'react'},
{label: 'Solid', value: 'solid'},
{label: 'Svelte', value: 'svelte'},
{label: 'Vue', value: 'vue'},
]
@ -895,22 +783,6 @@ test('should work', async ({ mount }) => {
</TabItem>
<TabItem value="solid">
```js
import { test, expect } from '@playwright/experimental-ct-solid';
import HelloWorld from './HelloWorld';
test.use({ viewport: { width: 500, height: 500 } });
test('should work', async ({ mount }) => {
const component = await mount(<HelloWorld msg="greetings" />);
await expect(component).toContainText('Greetings');
});
```
</TabItem>
</Tabs>
Additionally, it adds some config options you can use in your `playwright-ct.config.{ts,js}`.

View file

@ -115,8 +115,8 @@ test.describe('todo tests', () => {
### With fixtures
Fixtures have a number of advantages over before/after hooks:
- Fixtures **encapsulate** setup and teardown in the same place so it is easier to write.
- Fixtures are **reusable** between test files - you can define them once and use in all your tests. That's how Playwright's built-in `page` fixture works.
- Fixtures **encapsulate** setup and teardown in the same place so it is easier to write. So if you have an after hook that tears down what was created in a before hook, consider turning them into a fixture.
- Fixtures are **reusable** between test files - you can define them once and use in all your tests. That's how Playwright's built-in `page` fixture works. So if you have a helper function that is used in multiple tests, consider turning it into a fixture.
- Fixtures are **on-demand** - you can define as many fixtures as you'd like, and Playwright Test will setup only the ones needed by your test and nothing else.
- Fixtures are **composable** - they can depend on each other to provide complex behaviors.
- Fixtures are **flexible**. Tests can use any combinations of the fixtures to tailor precise environment they need, without affecting other tests.

View file

@ -129,7 +129,13 @@ You can use the `globalSetup` option in the [configuration file](./test-configur
Similarly, use `globalTeardown` to run something once after all the tests. Alternatively, let `globalSetup` return a function that will be used as a global teardown. You can pass data such as port number, authentication tokens, etc. from your global setup to your tests using environment variables.
:::note
Using `globalSetup` and `globalTeardown` will not produce traces or artifacts, and options like `headless` or `testIdAttribute` specified in the config file are not applied. If you want to produce traces and artifacts and respect config options, use [project dependencies](#option-1-project-dependencies).
Beware of `globalSetup` and `globalTeardown` caveats:
- These methods will not produce traces or artifacts unless explictly enabled, as described in [Capturing trace of failures during global setup](#capturing-trace-of-failures-during-global-setup).
- Options sush as `headless` or `testIdAttribute` specified in the config file are not applied,
- An uncaught exception thrown in `globalSetup` will prevent Playwright from running tests, and no test results will appear in reporters.
Consider using [project dependencies](#option-1-project-dependencies) to produce traces, artifacts, respect config options and get test results in reporters even in case of a setup failure.
:::
```js title="playwright.config.ts"

View file

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

View file

@ -111,16 +111,21 @@ def test_visit_admin_dashboard(page: Page):
If you're using VSCode with Pylance, these types can be inferred by enabling the `python.testing.pytestEnabled` setting so you don't need the type annotation.
### Configure slow mo
### Using multiple contexts
Run tests with slow mo with the `--slowmo` argument.
In order to simulate multiple users, you can create multiple [`BrowserContext`](./browser-contexts) instances.
```bash
pytest --slowmo 100
```py title="test_my_application.py"
from playwright.sync_api import Page, BrowserContext
from pytest_playwright.pytest_playwright import CreateContextCallback
def test_foo(page: Page, new_context: CreateContextCallback) -> None:
page.goto("https://example.com")
context = new_context()
page2 = context.new_page()
# page and page2 are in different contexts
```
Slows down Playwright operations by 100 milliseconds.
### Skip test by browser
```py title="test_my_application.py"
@ -198,7 +203,7 @@ def browser_context_args(browser_context_args):
}
```
### Device emulation
### Device emulation / BrowserContext option overrides
```py title="conftest.py"
import pytest
@ -254,3 +259,18 @@ def test_bing_is_working(page):
## Deploy to CI
See the [guides for CI providers](./ci.md) to deploy your tests to CI/CD.
## Async Fixtures
If you want to use async fixtures, you can use the [`pytest-playwright-asyncio`](https://pypi.org/project/pytest-playwright-asyncio/) plugin.
Make sure to use `pytest-asyncio>=0.24.0` and make your tests use of [`loop_scope=sesion`](https://pytest-asyncio.readthedocs.io/en/latest/how-to-guides/run_session_tests_in_same_loop.html).
```python
import pytest
from playwright.async_api import Page
@pytest.mark.asyncio(loop_scope="session")
async def test_foo(page: Page):
await page.goto("https://github.com")
# ...
```

View file

@ -37,7 +37,7 @@ When `fullyParallel: true` is enabled, Playwright Test runs individual tests in
**Sharding without fullyParallel**
Without the fullyParallel setting, Playwright Test defaults to file-level granularity, meaning entire test files are assigned to shards. In this case, the number of tests per file can greatly influence shard distribution. If your test files are not evenly sized (i.e., some files contain many more tests than others), certain shards may end up running significantly more tests, while others may run fewer or even none.
Without the fullyParallel setting, Playwright Test defaults to file-level granularity, meaning entire test files are assigned to shards (note that the same file may be assigned to different shards across different projects). In this case, the number of tests per file can greatly influence shard distribution. If your test files are not evenly sized (i.e., some files contain many more tests than others), certain shards may end up running significantly more tests, while others may run fewer or even none.
**Key Takeaways:**

View file

@ -16,6 +16,10 @@ test('example test', async ({ page }) => {
});
```
:::warning
Browser rendering can vary based on the host OS, version, settings, hardware, power source (battery vs. power adapter), headless mode, and other factors. For consistent screenshots, run tests in the same environment where the baseline screenshots were generated.
:::
## Generating screenshots
When you run above for the first time, test runner will say:

View file

@ -7,7 +7,7 @@ import LiteYouTube from '@site/src/components/LiteYouTube';
## Introduction
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. Filter tests by **text** or **@tag** or by **passed**, **failed** and **skipped** tests as well as by [**projects**](./test-projects) as set in your `playwright.config` file. See a full trace of your tests and hover back and forward over each action to see what was happening during each step and pop out the DOM snapshot to a separate window for a better debugging experience.
UI Mode lets you explore, run, and debug tests with a time travel experience complete with a watch mode. All test files are displayed in the testing sidebar, allowing you to expand each file and describe block to individually run, view, watch, and debug each test. Filter tests by **name**, [**projects**](./test-projects) (set in your `playwright.config` file), **@tag**, or by the execution status of **passed**, **failed**, and **skipped**. See a full trace of your tests and hover back and forward over each action to see what was happening during each step. You can also pop out the DOM snapshot of a given moment into a separate window for a better debugging experience.
<LiteYouTube
id="d0u6XhXknzU"

View file

@ -22,6 +22,7 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -112,6 +113,69 @@ public class ExampleTest : PageTest
}
```
</TabItem>
<TabItem value="xunit">
```csharp
using System.Reflection;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
using Xunit.Sdk;
namespace PlaywrightTests;
[WithTestName]
public class UnitTest1 : PageTest
{
public override async Task InitializeAsync()
{
await base.InitializeAsync().ConfigureAwait(false);
await Context.Tracing.StartAsync(new()
{
Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}",
Screenshots = true,
Snapshots = true,
Sources = true
});
}
public override async Task DisposeAsync()
{
await Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
Environment.CurrentDirectory,
"playwright-traces",
$"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip"
)
});
await base.DisposeAsync().ConfigureAwait(false);
}
[Fact]
public async Task GetStartedLink()
{
// ...
await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro");
}
}
public class WithTestNameAttribute : BeforeAfterTestAttribute
{
public static string CurrentTestName = string.Empty;
public static string CurrentClassName = string.Empty;
public override void Before(MethodInfo methodInfo)
{
CurrentTestName = methodInfo.Name;
CurrentClassName = methodInfo.DeclaringType!.Name;
}
public override void After(MethodInfo methodInfo)
{
}
}
```
</TabItem>
</Tabs>
@ -134,4 +198,4 @@ Check out our detailed guide on [Trace Viewer](/trace-viewer.md) to learn more a
## What's next
- [Run tests on CI with GitHub Actions](/ci-intro.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
- [Learn more about the MSTest, NUnit, and xUnit base classes](./test-runners.md)

View file

@ -22,7 +22,7 @@ Playwright Trace Viewer is a GUI tool that lets you explore recorded Playwright
## Recording a Trace
By default the [playwright.config](./trace-viewer.md#recording-a-trace-on-ci) file will contain the configuration needed to create a `trace.zip` file for each test. Traces are setup to run `on-first-retry` meaning they will be run on the first retry of a failed test. Also `retries` are set to 2 when running on CI and 0 locally. This means the traces will be recorded on the first retry of a failed test but not on the first run and not on the second retry.
By default the [playwright.config](./trace-viewer.md#tracing-on-ci) file will contain the configuration needed to create a `trace.zip` file for each test. Traces are setup to run `on-first-retry` meaning they will be run on the first retry of a failed test. Also `retries` are set to 2 when running on CI and 0 locally. This means the traces will be recorded on the first retry of a failed test but not on the first run and not on the second retry.
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
@ -45,7 +45,7 @@ npx playwright test --trace on
## Opening the HTML report
The HTML report shows you a report of all your tests that have been ran and on which browsers as well as how long they took. Tests can be filtered by passed tests, failed, flakey or skipped tests. You can also search for a particular test. Clicking on a test will open the detailed view where you can see more information on your tests such as the errors, the test steps and the trace.
The HTML report shows you a report of all your tests that have been run and on which browsers as well as how long they took. Tests can be filtered by passed tests, failed, flaky or skipped tests. You can also search for a particular test. Clicking on a test will open the detailed view where you can see more information on your tests such as the errors, the test steps and the trace.
```bash
npx playwright show-report

View file

@ -17,106 +17,67 @@ Playwright Trace Viewer is a GUI tool that helps you explore recorded Playwright
title="Viewing Playwright Traces"
/>
## Trace Viewer features
### Actions
## Opening Trace Viewer
In the Actions tab you can see what locator was used for every action and how long each one took to run. Hover over each action of your test and visually see the change in the DOM snapshot. Go back and forward in time and click an action to inspect and debug. Use the Before and After tabs to visually see what happened before and after the action.
You can open a saved trace using either the Playwright CLI or in the browser at [trace.playwright.dev](https://trace.playwright.dev). Make sure to add the full path to where your `trace.zip` file is located.
![actions tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/948b65cd-f0fd-4c7f-8e53-2c632b5a07f1)
```bash js
npx playwright show-trace path/to/trace.zip
```
**Selecting each action reveals:**
- action snapshots
- action log
- source code location
```bash java
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.zip"
```
### Screenshots
```bash python
playwright show-trace trace.zip
```
When tracing with the [`option: Tracing.start.screenshots`] option turned on (default), each trace records a screencast and renders it as a film strip. You can hover over the film strip to see a magnified image of for each action and state which helps you easily find the action you want to inspect.
```bash csharp
pwsh bin/Debug/netX/playwright.ps1 show-trace trace.zip
```
Double click on an action to see the time range for that action. You can use the slider in the timeline to increase the actions selected and these will be shown in the Actions tab and all console logs and network logs will be filtered to only show the logs for the actions selected.
### Using [trace.playwright.dev](https://trace.playwright.dev)
![timeline view in trace viewer](https://github.com/microsoft/playwright/assets/13063165/b04a7d75-54bb-4ab2-9e30-e76f6f74a2c8)
[trace.playwright.dev](https://trace.playwright.dev) is a statically hosted variant of the Trace Viewer. You can upload trace files using drag and drop or via the `Select file(s)` button.
Trace Viewer loads the trace entirely in your browser and does not transmit any data externally.
### Snapshots
<img width="1119" alt="Drop Playwright Trace to load" src="https://user-images.githubusercontent.com/13063165/194577918-b4d45726-2692-4093-8a28-9e73552617ef.png" />
When tracing with the [`option: Tracing.start.snapshots`] option turned on (default), Playwright captures a set of complete DOM snapshots for each action. Depending on the type of the action, it will capture:
### Viewing remote traces
| Type | Description |
|------|-------------|
|Before|A snapshot at the time action is called.|
|Action|A snapshot at the moment of the performed input. This type of snapshot is especially useful when exploring where exactly Playwright clicked.|
|After|A snapshot after the action.|
You can open remote traces directly using its URL. This makes it easy to view the remote trace without having to manually download the file from CI runs, for example.
Here is what the typical Action snapshot looks like:
```bash js
npx playwright show-trace https://example.com/trace.zip
```
![action tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/7168d549-eb0a-4964-9c93-483f03711fa9)
```bash java
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace https://example.com/trace.zip"
```
Notice how it highlights both, the DOM Node as well as the exact click position.
```bash python
playwright show-trace https://example.com/trace.zip
```
### Source
```bash csharp
pwsh bin/Debug/netX/playwright.ps1 show-trace https://example.com/trace.zip
```
When you click on an action in the sidebar, the line of code for that action is highlighted in the source panel.
When using [trace.playwright.dev](https://trace.playwright.dev), you can also pass the URL of your uploaded trace at some accessible storage (e.g. inside your CI) as a query parameter. CORS (Cross-Origin Resource Sharing) rules might apply.
![showing source code tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/daa8845d-c250-4923-aa7a-5d040da9adc5)
```txt
https://trace.playwright.dev/?trace=https://demo.playwright.dev/reports/todomvc/data/cb0fa77ebd9487a5c899f3ae65a7ffdbac681182.zip
```
### Call
The call tab shows you information about the action such as the time it took, what locator was used, if in strict mode and what key was used.
![showing call tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/95498580-f9dd-4932-a123-c37fe7cfc3c2)
### Log
See a full log of your test to better understand what Playwright is doing behind the scenes such as scrolling into view, waiting for element to be visible, enabled and stable and performing actions such as click, fill, press etc.
![showing log of tests in trace viewer](https://github.com/microsoft/playwright/assets/13063165/de621461-3bab-4140-b39d-9f02d6672dbf)
### Errors
If your test fails you will see the error messages for each test in the Errors tab. The timeline will also show a red line highlighting where the error occurred. You can also click on the source tab to see on which line of the source code the error is.
![showing errors in trace viewer](https://github.com/microsoft/playwright/assets/13063165/e9ef77b3-05d1-4df2-852c-981023723d34)
### Console
See console logs from the browser as well as from your test. Different icons are displayed to show you if the console log came from the browser or from the test file.
![showing log of tests in trace viewer](https://github.com/microsoft/playwright/assets/13063165/4107c08d-1eaf-421c-bdd4-9dd2aa641d4a)
Double click on an action from your test in the actions sidebar. This will filter the console to only show the logs that were made during that action. Click the *Show all* button to see all console logs again.
Use the timeline to filter actions, by clicking a start point and dragging to an ending point. The console tab will also be filtered to only show the logs that were made during the actions selected.
### Network
The Network tab shows you all the network requests that were made during your test. You can sort by different types of requests, status code, method, request, content type, duration and size. Click on a request to see more information about it such as the request headers, response headers, request body and response body.
![network requests tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/0a3d1671-8ccd-4f7a-a844-35f5eb37f236)
Double click on an action from your test in the actions sidebar. This will filter the network requests to only show the requests that were made during that action. Click the *Show all* button to see all network requests again.
Use the timeline to filter actions, by clicking a start point and dragging to an ending point. The network tab will also be filtered to only show the network requests that were made during the actions selected.
### Metadata
Next to the Actions tab you will find the Metadata tab which will show you more information on your test such as the Browser, viewport size, test duration and more.
![meta data in trace viewer](https://github.com/microsoft/playwright/assets/13063165/82ab3d33-1ec9-4b8a-9cf2-30a6e2d59091)
### Attachments
## Recording a trace
* langs: js
The "Attachments" tab allows you to explore attachments. If you're doing [visual regression testing](./test-snapshots.md), you'll be able to compare screenshots by examining the image diff, the actual image and the expected image. When you click on the expected image you can use the slider to slide one image over the other so you can easily see the differences in your screenshots.
![attachments tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/4386178a-5808-4fa8-9436-315350a23b04)
## Recording a trace locally
### Tracing locally
* langs: js
To record a trace during development mode set the `--trace` flag to `on` when running your tests. You can also use [UI Mode](./test-ui-mode.md) for a better developer experience.
To record a trace during development mode set the `--trace` flag to `on` when running your tests. You can also use [UI Mode](./test-ui-mode.md) for a better developer experience, as it traces each test automatically.
```bash
npx playwright test --trace on
@ -126,7 +87,7 @@ You can then open the HTML report and click on the trace icon to open the trace.
```bash
npx playwright show-report
```
## Recording a trace on CI
### Tracing on CI
* langs: js
Traces should be run on continuous integration on the first retry of a failed test
@ -254,6 +215,7 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -348,6 +310,70 @@ public class UnitTest1 : PageTest
}
```
</TabItem>
<TabItem value="xunit">
```csharp
using System.Reflection;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
using Xunit.Sdk;
namespace PlaywrightTests;
[WithTestName]
public class UnitTest1 : PageTest
{
public override async Task InitializeAsync()
{
await base.InitializeAsync().ConfigureAwait(false);
await Context.Tracing.StartAsync(new()
{
Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}",
Screenshots = true,
Snapshots = true,
Sources = true
});
}
public override async Task DisposeAsync()
{
await Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
Environment.CurrentDirectory,
"playwright-traces",
$"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip"
)
});
await base.DisposeAsync().ConfigureAwait(false);
}
[Fact]
public async Task GetStartedLink()
{
// ...
await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro");
}
}
public class WithTestNameAttribute : BeforeAfterTestAttribute
{
public static string CurrentTestName = string.Empty;
public static string CurrentClassName = string.Empty;
public override void Before(MethodInfo methodInfo)
{
CurrentTestName = methodInfo.Name;
CurrentClassName = methodInfo.DeclaringType!.Name;
}
public override void After(MethodInfo methodInfo)
{
}
}
```
</TabItem>
</Tabs>
@ -365,6 +391,7 @@ Setup your tests to record a trace only when the test fails:
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -459,60 +486,165 @@ public class ExampleTest : PageTest
}
```
</TabItem>
<TabItem value="xunit">
```csharp
using System.Reflection;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
using Xunit.Sdk;
namespace PlaywrightTests;
[WithTestName]
public class UnitTest1 : PageTest
{
public override async Task InitializeAsync()
{
await base.InitializeAsync().ConfigureAwait(false);
await Context.Tracing.StartAsync(new()
{
Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}",
Screenshots = true,
Snapshots = true,
Sources = true
});
}
public override async Task DisposeAsync()
{
await Context.Tracing.StopAsync(new()
{
Path = !TestOk ? Path.Combine(
Environment.CurrentDirectory,
"playwright-traces",
$"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip"
) : null
});
await base.DisposeAsync().ConfigureAwait(false);
}
[Fact]
public async Task GetStartedLink()
{
// ...
await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro");
}
}
public class WithTestNameAttribute : BeforeAfterTestAttribute
{
public static string CurrentTestName = string.Empty;
public static string CurrentClassName = string.Empty;
public override void Before(MethodInfo methodInfo)
{
CurrentTestName = methodInfo.Name;
CurrentClassName = methodInfo.DeclaringType!.Name;
}
public override void After(MethodInfo methodInfo)
{
}
}
```
</TabItem>
</Tabs>
## Opening the trace
## Trace Viewer features
### Actions
You can open the saved trace using the Playwright CLI or in your browser on [`trace.playwright.dev`](https://trace.playwright.dev). Make sure to add the full path to where your `trace.zip` file is located.
In the Actions tab you can see what locator was used for every action and how long each one took to run. Hover over each action of your test and visually see the change in the DOM snapshot. Go back and forward in time and click an action to inspect and debug. Use the Before and After tabs to visually see what happened before and after the action.
```bash js
npx playwright show-trace path/to/trace.zip
```
![actions tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/948b65cd-f0fd-4c7f-8e53-2c632b5a07f1)
```bash java
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.zip"
```
**Selecting each action reveals:**
- Action snapshots
- Action log
- Source code location
```bash python
playwright show-trace trace.zip
```
### Screenshots
```bash csharp
pwsh bin/Debug/netX/playwright.ps1 show-trace trace.zip
```
When tracing with the [`option: Tracing.start.screenshots`] option turned on (default), each trace records a screencast and renders it as a film strip. You can hover over the film strip to see a magnified image of for each action and state which helps you easily find the action you want to inspect.
## Using [trace.playwright.dev](https://trace.playwright.dev)
Double click on an action to see the time range for that action. You can use the slider in the timeline to increase the actions selected and these will be shown in the Actions tab and all console logs and network logs will be filtered to only show the logs for the actions selected.
[trace.playwright.dev](https://trace.playwright.dev) is a statically hosted variant of the Trace Viewer. You can upload trace files using drag and drop.
![timeline view in trace viewer](https://github.com/microsoft/playwright/assets/13063165/b04a7d75-54bb-4ab2-9e30-e76f6f74a2c8)
<img width="1119" alt="Drop Playwright Trace to load" src="https://user-images.githubusercontent.com/13063165/194577918-b4d45726-2692-4093-8a28-9e73552617ef.png" />
### Snapshots
## Viewing remote traces
When tracing with the [`option: Tracing.start.snapshots`] option turned on (default), Playwright captures a set of complete DOM snapshots for each action. Depending on the type of the action, it will capture:
You can open remote traces using its URL. They could be generated on a CI run which makes it easy to view the remote trace without having to manually download the file.
| Type | Description |
|------|-------------|
|Before|A snapshot at the time action is called.|
|Action|A snapshot at the moment of the performed input. This type of snapshot is especially useful when exploring where exactly Playwright clicked.|
|After|A snapshot after the action.|
```bash js
npx playwright show-trace https://example.com/trace.zip
```
Here is what the typical Action snapshot looks like:
```bash java
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace https://example.com/trace.zip"
```
![action tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/7168d549-eb0a-4964-9c93-483f03711fa9)
```bash python
playwright show-trace https://example.com/trace.zip
```
Notice how it highlights both, the DOM Node as well as the exact click position.
```bash csharp
pwsh bin/Debug/netX/playwright.ps1 show-trace https://example.com/trace.zip
```
### Source
When you click on an action in the sidebar, the line of code for that action is highlighted in the source panel.
![showing source code tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/daa8845d-c250-4923-aa7a-5d040da9adc5)
### Call
The call tab shows you information about the action such as the time it took, what locator was used, if in strict mode and what key was used.
![showing call tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/95498580-f9dd-4932-a123-c37fe7cfc3c2)
### Log
See a full log of your test to better understand what Playwright is doing behind the scenes such as scrolling into view, waiting for element to be visible, enabled and stable and performing actions such as click, fill, press etc.
![showing log of tests in trace viewer](https://github.com/microsoft/playwright/assets/13063165/de621461-3bab-4140-b39d-9f02d6672dbf)
### Errors
If your test fails you will see the error messages for each test in the Errors tab. The timeline will also show a red line highlighting where the error occurred. You can also click on the source tab to see on which line of the source code the error is.
![showing errors in trace viewer](https://github.com/microsoft/playwright/assets/13063165/e9ef77b3-05d1-4df2-852c-981023723d34)
### Console
See console logs from the browser as well as from your test. Different icons are displayed to show you if the console log came from the browser or from the test file.
![showing log of tests in trace viewer](https://github.com/microsoft/playwright/assets/13063165/4107c08d-1eaf-421c-bdd4-9dd2aa641d4a)
Double click on an action from your test in the actions sidebar. This will filter the console to only show the logs that were made during that action. Click the *Show all* button to see all console logs again.
Use the timeline to filter actions, by clicking a start point and dragging to an ending point. The console tab will also be filtered to only show the logs that were made during the actions selected.
You can also pass the URL of your uploaded trace (e.g. inside your CI) from some accessible storage as a parameter. CORS (Cross-Origin Resource Sharing) rules might apply.
### Network
```txt
https://trace.playwright.dev/?trace=https://demo.playwright.dev/reports/todomvc/data/cb0fa77ebd9487a5c899f3ae65a7ffdbac681182.zip
```
The Network tab shows you all the network requests that were made during your test. You can sort by different types of requests, status code, method, request, content type, duration and size. Click on a request to see more information about it such as the request headers, response headers, request body and response body.
![network requests tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/0a3d1671-8ccd-4f7a-a844-35f5eb37f236)
Double click on an action from your test in the actions sidebar. This will filter the network requests to only show the requests that were made during that action. Click the *Show all* button to see all network requests again.
Use the timeline to filter actions, by clicking a start point and dragging to an ending point. The network tab will also be filtered to only show the network requests that were made during the actions selected.
### Metadata
Next to the Actions tab you will find the Metadata tab which will show you more information on your test such as the Browser, viewport size, test duration and more.
![meta data in trace viewer](https://github.com/microsoft/playwright/assets/13063165/82ab3d33-1ec9-4b8a-9cf2-30a6e2d59091)
### Attachments
* langs: js
The "Attachments" tab allows you to explore attachments. If you're doing [visual regression testing](./test-snapshots.md), you'll be able to compare screenshots by examining the image diff, the actual image and the expected image. When you click on the expected image you can use the slider to slide one image over the other so you can easily see the differences in your screenshots.
![attachments tab in trace viewer](https://github.com/microsoft/playwright/assets/13063165/4386178a-5808-4fa8-9436-315350a23b04)

View file

@ -39,6 +39,7 @@ Take a look at the following example to see how to write a test.
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -117,6 +118,40 @@ public class ExampleTest : PageTest
}
```
</TabItem>
<TabItem value="xunit">
```csharp title="UnitTest1.cs"
using System.Text.RegularExpressions;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
namespace PlaywrightTests;
public class UnitTest1: PageTest
{
[Fact]
public async Task HasTitle()
{
await Page.GotoAsync("https://playwright.dev");
// Expect a title "to contain" a substring.
await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
}
[Fact]
public async Task GetStartedLink()
{
await Page.GotoAsync("https://playwright.dev");
// Click the get started link.
await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync();
// Expects page to have a heading with the name of Installation.
await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync();
}
}
```
</TabItem>
</Tabs>
@ -204,6 +239,7 @@ The Playwright NUnit and MSTest test framework base classes will isolate each te
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
@ -248,23 +284,43 @@ public class ExampleTest : PageTest
}
```
</TabItem>
<TabItem value="xunit">
```csharp title="UnitTest1.cs"
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
namespace PlaywrightTests;
public class UnitTest1: PageTest
{
[Fact]
public async Task BasicTest()
{
await Page.GotoAsync("https://playwright.dev");
}
}
```
</TabItem>
</Tabs>
## Using Test Hooks
You can use `SetUp`/`TearDown` in NUnit or `TestInitialize`/`TestCleanup` in MSTest to prepare and clean up your test environment:
<Tabs
groupId="test-runners"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
{label: 'xUnit', value: 'xunit'},
]
}>
<TabItem value="nunit">
You can use `SetUp`/`TearDown` to prepare and clean up your test environment:
```csharp title="UnitTest1.cs"
using System.Threading.Tasks;
using Microsoft.Playwright.NUnit;
@ -294,6 +350,8 @@ public class ExampleTest : PageTest
</TabItem>
<TabItem value="mstest">
You can use `TestInitialize`/`TestCleanup` to prepare and clean up your test environment:
```csharp title="UnitTest1.cs"
using System.Threading.Tasks;
using Microsoft.Playwright.MSTest;
@ -319,6 +377,39 @@ public class ExampleTest : PageTest
}
```
</TabItem>
<TabItem value="xunit">
You can use `InitializeAsync`/`DisposeAsync` to prepare and clean up your test environment:
```csharp title="UnitTest1.cs"
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
namespace PlaywrightTests;
public class UnitTest1: PageTest
{
[Fact]
public async Task MainNavigation()
{
// Assertions use the expect API.
await Expect(Page).ToHaveURLAsync("https://playwright.dev/");
}
override public async Task InitializeAsync()
{
await base.InitializeAsync();
await Page.GotoAsync("https://playwright.dev");
}
public override async Task DisposeAsync()
{
Console.WriteLine("After each test cleanup");
await base.DisposeAsync();
}
}
```
</TabItem>
</Tabs>
@ -328,4 +419,4 @@ public class ExampleTest : PageTest
- [Generate tests with Codegen](./codegen-intro.md)
- [See a trace of your tests](./trace-viewer-intro.md)
- [Run tests on CI](./ci-intro.md)
- [Learn more about the MSTest and NUnit base classes](./test-runners.md)
- [Learn more about the MSTest, NUnit, or xUnit base classes](./test-runners.md)

573
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "playwright-internal",
"private": true,
"version": "1.49.0-next",
"version": "1.50.0-next",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",
@ -66,7 +66,7 @@
"@types/codemirror": "^5.60.7",
"@types/formidable": "^2.0.4",
"@types/immutable": "^3.8.7",
"@types/node": "^18.19.39",
"@types/node": "^18.19.68",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@types/ws": "^8.5.3",
@ -99,10 +99,10 @@
"react": "^18.1.0",
"react-dom": "^18.1.0",
"ssim.js": "^3.5.0",
"typescript": "^5.5.3",
"typescript": "^5.7.2",
"vite": "^5.4.6",
"ws": "^8.17.1",
"xml2js": "^0.5.0",
"yaml": "^2.5.1"
"yaml": "^2.6.0"
}
}

View file

@ -2,13 +2,5 @@
"name": "html-reporter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build && tsc",
"preview": "vite preview"
},
"dependencies": {
"ansi-to-html": "^0.7.2"
}
"type": "module"
}

View file

@ -48,3 +48,14 @@ test('setExpanded is called', async ({ mount }) => {
await component.getByText('Title').click();
expect(expandedValues).toEqual([true]);
});
test('setExpanded should work', async ({ mount }) => {
const component = await mount(<AutoChip header='Title' initialExpanded={false}>
Body
</AutoChip>);
await component.getByText('Title').click();
await expect(component).toMatchAriaSnapshot(`
- button "Title" [expanded]
- region: Body
`);
});

View file

@ -20,6 +20,7 @@ import './colors.css';
import './common.css';
import * as icons from './icons';
import { clsx } from '@web/uiUtils';
import { type AnchorID, useAnchor } from './links';
export const Chip: React.FC<{
header: JSX.Element | string,
@ -28,10 +29,13 @@ export const Chip: React.FC<{
setExpanded?: (expanded: boolean) => void,
children?: any,
dataTestId?: string,
targetRef?: React.RefObject<HTMLDivElement>,
}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId, targetRef }) => {
return <div className='chip' data-testid={dataTestId} ref={targetRef}>
}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId }) => {
const id = React.useId();
return <div className='chip' data-testid={dataTestId}>
<div
role='button'
aria-expanded={!!expanded}
aria-controls={id}
className={clsx('chip-header', setExpanded && ' expanded-' + expanded)}
onClick={() => setExpanded?.(!expanded)}
title={typeof header === 'string' ? header : undefined}>
@ -39,7 +43,7 @@ export const Chip: React.FC<{
{setExpanded && !expanded && icons.rightArrow()}
{header}
</div>
{(!setExpanded || expanded) && <div className={clsx('chip-body', noInsets && 'chip-body-no-insets')}>{children}</div>}
{(!setExpanded || expanded) && <div id={id} role='region' className={clsx('chip-body', noInsets && 'chip-body-no-insets')}>{children}</div>}
</div>;
};
@ -49,16 +53,17 @@ export const AutoChip: React.FC<{
noInsets?: boolean,
children?: any,
dataTestId?: string,
targetRef?: React.RefObject<HTMLDivElement>,
}> = ({ header, initialExpanded, noInsets, children, dataTestId, targetRef }) => {
const [expanded, setExpanded] = React.useState(initialExpanded || initialExpanded === undefined);
revealOnAnchorId?: AnchorID,
}> = ({ header, initialExpanded, noInsets, children, dataTestId, revealOnAnchorId }) => {
const [expanded, setExpanded] = React.useState(initialExpanded ?? true);
const onReveal = React.useCallback(() => setExpanded(true), []);
useAnchor(revealOnAnchorId, onReveal);
return <Chip
header={header}
expanded={expanded}
setExpanded={setExpanded}
noInsets={noInsets}
dataTestId={dataTestId}
targetRef={targetRef}
>
{children}
</Chip>;

View file

@ -46,6 +46,10 @@ svg {
position: relative;
}
.hidden {
visibility: hidden;
}
.d-flex {
display: flex !important;
}

View file

@ -33,6 +33,11 @@ test('should render counters', async ({ mount }) => {
await expect(component.locator('a', { hasText: 'Failed' }).locator('.counter')).toHaveText('31');
await expect(component.locator('a', { hasText: 'Flaky' }).locator('.counter')).toHaveText('17');
await expect(component.locator('a', { hasText: 'Skipped' }).locator('.counter')).toHaveText('10');
await expect(component).toMatchAriaSnapshot(`
- navigation:
- link "All 90"
- text: Passed 42 Failed 31 Flaky 17 Skipped 10
`);
});
test('should toggle filters', async ({ page, mount }) => {
@ -59,6 +64,6 @@ test('should toggle filters', async ({ page, mount }) => {
await expect(page).toHaveURL(/#\?q=s:flaky/);
await component.locator('a', { hasText: 'Skipped' }).click();
await expect(page).toHaveURL(/#\?q=s:skipped/);
await component.getByRole('searchbox').fill('annot:annotation type=annotation description');
await component.getByRole('textbox').fill('annot:annotation type=annotation description');
expect(filters).toEqual(['', 's:passed', 's:failed', 's:flaky', 's:skipped', 'annot:annotation type=annotation description']);
});

View file

@ -49,12 +49,14 @@ export const HeaderView: React.FC<React.PropsWithChildren<{
<form className='subnav-search' onSubmit={
event => {
event.preventDefault();
navigate(`#?q=${filterText ? encodeURIComponent(filterText) : ''}`);
const url = new URL(window.location.href);
url.hash = filterText ? '?' + new URLSearchParams({ q: filterText }) : '';
navigate(url);
}
}>
{icons.search()}
{/* Use navigationId to reset defaultValue */}
<input type='search' spellCheck={false} className='form-control subnav-search-input input-contrast width-full' value={filterText} onChange={e => {
<input spellCheck={false} className='form-control subnav-search-input input-contrast width-full' value={filterText} onChange={e => {
setFilterText(e.target.value);
}}></input>
</form>

View file

@ -15,7 +15,7 @@
*/
import type { HTMLReport } from './types';
import type zip from '@zip.js/zip.js';
import type * as zip from '@zip.js/zip.js';
// @ts-ignore
import * as zipImport from '@zip.js/zip.js/lib/zip-no-worker-inflate.js';
import * as React from 'react';
@ -50,12 +50,33 @@ window.onload = () => {
ReactDOM.createRoot(document.querySelector('#root')!).render(<ReportLoader />);
};
const kPlaywrightReportStorageForHMR = 'playwrightReportStorageForHMR';
class ZipReport implements LoadedReport {
private _entries = new Map<string, zip.Entry>();
private _json!: HTMLReport;
async load() {
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader(window.playwrightReportBase64!), { useWebWorkers: false });
const zipURI = await new Promise<string>(resolve => {
if (window.playwrightReportBase64)
return resolve(window.playwrightReportBase64);
if (window.opener) {
window.addEventListener('message', event => {
if (event.source === window.opener) {
localStorage.setItem(kPlaywrightReportStorageForHMR, event.data);
resolve(event.data);
}
}, { once: true });
window.opener.postMessage('ready', '*');
} else {
const oldReport = localStorage.getItem(kPlaywrightReportStorageForHMR);
if (oldReport)
return resolve(oldReport);
alert('couldnt find report, something with HMR is broken');
}
});
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader(zipURI), { useWebWorkers: false });
for (const entry of await zipReader.getEntries())
this._entries.set(entry.filename, entry);
this._json = await this.entry('report.json') as HTMLReport;

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
import type { TestAttachment } from './types';
import type { TestAttachment, TestCase, TestCaseSummary, TestResult, TestResultSummary } from './types';
import * as React from 'react';
import * as icons from './icons';
import { TreeItem } from './treeItem';
@ -23,7 +23,7 @@ import './links.css';
import { linkifyText } from '@web/renderUtils';
import { clsx } from '@web/uiUtils';
export function navigate(href: string) {
export function navigate(href: string | URL) {
window.history.pushState({}, '', href);
const navEvent = new PopStateEvent('popstate');
window.dispatchEvent(navEvent);
@ -72,6 +72,7 @@ export const AttachmentLink: React.FunctionComponent<{
linkName?: string,
openInNewTab?: boolean,
}> = ({ attachment, href, linkName, openInNewTab }) => {
const isAnchored = useIsAnchored('attachment-' + attachment.name);
return <TreeItem title={<span>
{attachment.contentType === kMissingContentType ? icons.warning() : icons.attachment()}
{attachment.path && <a href={href || attachment.path} download={downloadFileNameForAttachment(attachment)}>{linkName || attachment.name}</a>}
@ -82,7 +83,7 @@ export const AttachmentLink: React.FunctionComponent<{
)}
</span>} loadChildren={attachment.body ? () => {
return [<div key={1} className='attachment-body'><CopyToClipboard value={attachment.body!}/>{linkifyText(attachment.body!)}</div>];
} : undefined} depth={0} style={{ lineHeight: '32px' }}></TreeItem>;
} : undefined} depth={0} style={{ lineHeight: '32px' }} selected={isAnchored}></TreeItem>;
};
export const SearchParamsContext = React.createContext<URLSearchParams>(new URLSearchParams(window.location.hash.slice(1)));
@ -113,3 +114,49 @@ export function generateTraceUrl(traces: TestAttachment[]) {
}
const kMissingContentType = 'x-playwright/missing';
export type AnchorID = string | string[] | ((id: string) => boolean) | undefined;
export function useAnchor(id: AnchorID, onReveal: () => void) {
const searchParams = React.useContext(SearchParamsContext);
const isAnchored = useIsAnchored(id);
React.useEffect(() => {
if (isAnchored)
onReveal();
}, [isAnchored, onReveal, searchParams]);
}
export function useIsAnchored(id: AnchorID) {
const searchParams = React.useContext(SearchParamsContext);
const anchor = searchParams.get('anchor');
if (anchor === null)
return false;
if (typeof id === 'undefined')
return false;
if (typeof id === 'string')
return id === anchor;
if (Array.isArray(id))
return id.includes(anchor);
return id(anchor);
}
export function Anchor({ id, children }: React.PropsWithChildren<{ id: AnchorID }>) {
const ref = React.useRef<HTMLDivElement>(null);
const onAnchorReveal = React.useCallback(() => {
ref.current?.scrollIntoView({ block: 'start', inline: 'start' });
}, []);
useAnchor(id, onAnchorReveal);
return <div ref={ref}>{children}</div>;
}
export function testResultHref({ test, result, anchor }: { test?: TestCase | TestCaseSummary, result?: TestResult | TestResultSummary, anchor?: string }) {
const params = new URLSearchParams();
if (test)
params.set('testId', test.testId);
if (test && result)
params.set('run', '' + test.results.indexOf(result as any));
if (anchor)
params.set('anchor', anchor);
return `#?` + params;
}

View file

@ -101,7 +101,6 @@ const TestCaseViewLoader: React.FC<{
const searchParams = React.useContext(SearchParamsContext);
const [test, setTest] = React.useState<TestCase | undefined>();
const testId = searchParams.get('testId');
const anchor = (searchParams.get('anchor') || '') as 'video' | 'diff' | '';
const run = +(searchParams.get('run') || '0');
const { prev, next } = React.useMemo(() => {
@ -133,7 +132,6 @@ const TestCaseViewLoader: React.FC<{
next={next}
prev={prev}
test={test}
anchor={anchor}
run={run}
/>;
};

View file

@ -63,7 +63,7 @@ const testCase: TestCase = {
};
test('should render test case', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} prev={undefined} next={undefined} run={0} anchor=''></TestCaseView>);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} prev={undefined} next={undefined} run={0}></TestCaseView>);
await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible();
await expect(component.getByText('Hidden annotation')).toBeHidden();
await component.getByText('Annotations').click();
@ -79,7 +79,7 @@ test('should render test case', async ({ mount }) => {
test('should render copy buttons for annotations', async ({ mount, page, context }) => {
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} prev={undefined} next={undefined} run={0} anchor=''></TestCaseView>);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} prev={undefined} next={undefined} run={0}></TestCaseView>);
await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible();
await component.getByText('Annotation text', { exact: false }).first().hover();
await expect(component.locator('.test-case-annotation').getByLabel('Copy to clipboard').first()).toBeVisible();
@ -108,7 +108,7 @@ const annotationLinkRenderingTestCase: TestCase = {
};
test('should correctly render links in annotations', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={annotationLinkRenderingTestCase} prev={undefined} next={undefined} run={0} anchor=''></TestCaseView>);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={annotationLinkRenderingTestCase} prev={undefined} next={undefined} run={0}></TestCaseView>);
const firstLink = await component.getByText('https://playwright.dev/docs/intro').first();
await expect(firstLink).toBeVisible();
@ -154,7 +154,7 @@ const resultWithAttachment: TestResult = {
const attachmentLinkRenderingTestCase: TestCase = {
testId: 'testid',
title: 'My test',
path: [],
path: ['group'],
projectName: 'chromium',
location: { file: 'test.spec.ts', line: 42, column: 0 },
tags: [],
@ -181,24 +181,32 @@ const testCaseSummary: TestCaseSummary = {
test('should correctly render links in attachments', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} prev={undefined} next={undefined} run={0} anchor=''></TestCaseView>);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} prev={undefined} next={undefined} run={0}></TestCaseView>);
await component.getByText('first attachment').click();
const body = await component.getByText('The body with https://playwright.dev/docs/intro link');
await expect(body).toBeVisible();
await expect(body.locator('a').filter({ hasText: 'playwright.dev' })).toHaveAttribute('href', 'https://playwright.dev/docs/intro');
await expect(body.locator('a').filter({ hasText: 'github.com' })).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284');
await expect(component).toMatchAriaSnapshot(`
- link "https://playwright.dev/docs/intro"
- link "https://github.com/microsoft/playwright/issues/31284"
`);
});
test('should correctly render links in attachment name', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} prev={undefined} next={undefined} run={0} anchor=''></TestCaseView>);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} prev={undefined} next={undefined} run={0}></TestCaseView>);
const link = component.getByText('attachment with inline link').locator('a');
await expect(link).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284');
await expect(link).toHaveText('https://github.com/microsoft/playwright/issues/31284');
await expect(component).toMatchAriaSnapshot(`
- link /https:\\/\\/github\\.com\\/microsoft\\/playwright\\/issues\\/\\d+/
`);
});
test('should correctly render prev and next', async ({ mount }) => {
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} prev={testCaseSummary} next={testCaseSummary} run={0} anchor=''></TestCaseView>);
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={attachmentLinkRenderingTestCase} prev={testCaseSummary} next={testCaseSummary} run={0}></TestCaseView>);
await expect(component).toMatchAriaSnapshot(`
- text: group
- link "« previous"
- link "next »"
- text: "My test test.spec.ts:42 10ms"

View file

@ -19,7 +19,7 @@ import * as React from 'react';
import { TabbedPane } from './tabbedPane';
import { AutoChip } from './chip';
import './common.css';
import { Link, ProjectLink, SearchParamsContext } from './links';
import { Link, ProjectLink, SearchParamsContext, testResultHref } from './links';
import { statusIcon } from './statusIcon';
import './testCaseView.css';
import { TestResultView } from './testResultView';
@ -33,9 +33,8 @@ export const TestCaseView: React.FC<{
test: TestCase | undefined,
next: TestCaseSummary | undefined,
prev: TestCaseSummary | undefined,
anchor: 'video' | 'diff' | '',
run: number,
}> = ({ projectNames, test, run, anchor, next, prev }) => {
}> = ({ projectNames, test, run, next, prev }) => {
const [selectedResultIndex, setSelectedResultIndex] = React.useState(run);
const searchParams = React.useContext(SearchParamsContext);
const filterParam = searchParams.has('q') ? '&q=' + searchParams.get('q') : '';
@ -51,12 +50,13 @@ export const TestCaseView: React.FC<{
}, [test?.annotations]);
return <div className='test-case-column vbox'>
<div className='hbox'>
{prev && <Link href={`#?testId=${prev.testId}${filterParam}`}>« previous</Link>}
{test && <div className='hbox'>
<div className='test-case-path'>{test.path.join(' ')}</div>
<div style={{ flex: 'auto' }}></div>
{next && <Link href={`#?testId=${next.testId}${filterParam}`}>next »</Link>}
</div>
{test && <div className='test-case-path'>{test.path.join(' ')}</div>}
<div className={clsx(!prev && 'hidden')}><Link href={testResultHref({ test: prev }) + filterParam}>« previous</Link></div>
<div style={{ width: 10 }}></div>
<div className={clsx(!next && 'hidden')}><Link href={testResultHref({ test: next }) + filterParam}>next »</Link></div>
</div>}
{test && <div className='test-case-title'>{test?.title}</div>}
{test && <div className='hbox'>
<div className='test-case-location'>
@ -78,7 +78,7 @@ export const TestCaseView: React.FC<{
test.results.map((result, index) => ({
id: String(index),
title: <div style={{ display: 'flex', alignItems: 'center' }}>{statusIcon(result.status)} {retryLabel(index)}</div>,
render: () => <TestResultView test={test!} result={result} anchor={anchor}></TestResultView>
render: () => <TestResultView test={test!} result={result} />
})) || []} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
</div>;
};

View file

@ -14,6 +14,8 @@
limitations under the License.
*/
@import '@web/third_party/vscode/colors.css';
.test-error-view {
white-space: pre;
overflow: auto;

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
import ansi2html from 'ansi-to-html';
import { ansi2html } from '@web/ansi2html';
import * as React from 'react';
import './testErrorView.css';
import type { ImageDiff } from '@web/shared/imageDiffView';
@ -25,7 +25,7 @@ export const TestErrorView: React.FC<{
testId?: string;
}> = ({ error, testId }) => {
const html = React.useMemo(() => ansiErrorToHtml(error), [error]);
return <div className='test-error-view test-error-text' data-testId={testId} dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
return <div className='test-error-view test-error-text' data-testid={testId} dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
};
export const TestScreenshotErrorView: React.FC<{
@ -43,33 +43,9 @@ export const TestScreenshotErrorView: React.FC<{
};
function ansiErrorToHtml(text?: string): string {
const config: any = {
const defaultColors = {
bg: 'var(--color-canvas-subtle)',
fg: 'var(--color-fg-default)',
};
config.colors = ansiColors;
return new ansi2html(config).toHtml(escapeHTML(text || ''));
}
const ansiColors = {
0: '#000',
1: '#C00',
2: '#0C0',
3: '#C50',
4: '#00C',
5: '#C0C',
6: '#0CC',
7: '#CCC',
8: '#555',
9: '#F55',
10: '#5F5',
11: '#FF5',
12: '#55F',
13: '#F5F',
14: '#5FF',
15: '#FFF'
};
function escapeHTML(text: string): string {
return text.replace(/[&"<>]/g, c => ({ '&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;' }[c]!));
return ansi2html(text || '', defaultColors);
}

View file

@ -19,7 +19,7 @@ import * as React from 'react';
import { hashStringToInt, msToString } from './utils';
import { Chip } from './chip';
import { filterWithToken } from './filter';
import { generateTraceUrl, Link, navigate, ProjectLink, SearchParamsContext } from './links';
import { generateTraceUrl, Link, navigate, ProjectLink, SearchParamsContext, testResultHref } from './links';
import { statusIcon } from './statusIcon';
import './testFileView.css';
import { video, image, trace } from './icons';
@ -48,7 +48,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
{statusIcon(test.outcome)}
</span>
<span>
<Link href={`#?testId=${test.testId}${filterParam}`} title={[...test.path, test.title].join(' ')}>
<Link href={testResultHref({ test }) + filterParam} title={[...test.path, test.title].join(' ')}>
<span className='test-file-title'>{[...test.path, test.title].join(' ')}</span>
</Link>
{projectNames.length > 1 && !!test.projectName &&
@ -59,7 +59,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
<span data-testid='test-duration' style={{ minWidth: '50px', textAlign: 'right' }}>{msToString(test.duration)}</span>
</div>
<div className='test-file-details-row'>
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' ')} className='test-file-path-link'>
<Link href={testResultHref({ test })} title={[...test.path, test.title].join(' ')} className='test-file-path-link'>
<span className='test-file-path'>{test.location.file}:{test.location.line}</span>
</Link>
{imageDiffBadge(test)}
@ -72,15 +72,17 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
};
function imageDiffBadge(test: TestCaseSummary): JSX.Element | undefined {
const resultWithImageDiff = test.results.find(result => result.attachments.some(attachment => {
return attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/);
}));
return resultWithImageDiff ? <Link href={`#?testId=${test.testId}&anchor=diff&run=${test.results.indexOf(resultWithImageDiff)}`} title='View images' className='test-file-badge'>{image()}</Link> : undefined;
for (const result of test.results) {
for (const attachment of result.attachments) {
if (attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/))
return <Link href={testResultHref({ test, result, anchor: `attachment-${attachment.name}` })} title='View images' className='test-file-badge'>{image()}</Link>;
}
}
}
function videoBadge(test: TestCaseSummary): JSX.Element | undefined {
const resultWithVideo = test.results.find(result => result.attachments.some(attachment => attachment.name === 'video'));
return resultWithVideo ? <Link href={`#?testId=${test.testId}&anchor=video&run=${test.results.indexOf(resultWithVideo)}`} title='View video' className='test-file-badge'>{video()}</Link> : undefined;
return resultWithVideo ? <Link href={testResultHref({ test, result: resultWithVideo, anchor: 'attachment-video' })} title='View video' className='test-file-badge'>{video()}</Link> : undefined;
}
function traceBadge(test: TestCaseSummary): JSX.Element | undefined {

View file

@ -20,15 +20,20 @@ import { TreeItem } from './treeItem';
import { msToString } from './utils';
import { AutoChip } from './chip';
import { traceImage } from './images';
import { AttachmentLink, generateTraceUrl } from './links';
import { Anchor, AttachmentLink, generateTraceUrl, testResultHref } from './links';
import { statusIcon } from './statusIcon';
import type { ImageDiff } from '@web/shared/imageDiffView';
import { ImageDiffView } from '@web/shared/imageDiffView';
import { TestErrorView, TestScreenshotErrorView } from './testErrorView';
import * as icons from './icons';
import './testResultView.css';
function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
const snapshotNameToImageDiff = new Map<string, ImageDiff>();
interface ImageDiffWithAnchors extends ImageDiff {
anchors: string[];
}
function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiffWithAnchors[] {
const snapshotNameToImageDiff = new Map<string, ImageDiffWithAnchors>();
for (const attachment of screenshots) {
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
if (!match)
@ -37,9 +42,10 @@ function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
const snapshotName = name + extension;
let imageDiff = snapshotNameToImageDiff.get(snapshotName);
if (!imageDiff) {
imageDiff = { name: snapshotName };
imageDiff = { name: snapshotName, anchors: [`attachment-${name}`] };
snapshotNameToImageDiff.set(snapshotName, imageDiff);
}
imageDiff.anchors.push(`attachment-${attachment.name}`);
if (category === 'actual')
imageDiff.actual = { attachment };
if (category === 'expected')
@ -64,36 +70,21 @@ function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
export const TestResultView: React.FC<{
test: TestCase,
result: TestResult,
anchor: 'video' | 'diff' | '',
}> = ({ result, anchor }) => {
const { screenshots, videos, traces, otherAttachments, diffs, errors, htmls } = React.useMemo(() => {
}> = ({ test, result }) => {
const { screenshots, videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors } = React.useMemo(() => {
const attachments = result?.attachments || [];
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
const screenshotAnchors = [...screenshots].map(a => `attachment-${a.name}`);
const videos = attachments.filter(a => a.contentType.startsWith('video/'));
const traces = attachments.filter(a => a.name === 'trace');
const htmls = attachments.filter(a => a.contentType.startsWith('text/html'));
const otherAttachments = new Set<TestAttachment>(attachments);
[...screenshots, ...videos, ...traces, ...htmls].forEach(a => otherAttachments.delete(a));
[...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a));
const otherAttachmentAnchors = [...otherAttachments].map(a => `attachment-${a.name}`);
const diffs = groupImageDiffs(screenshots);
const errors = classifyErrors(result.errors, diffs);
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors, htmls };
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors };
}, [result]);
const videoRef = React.useRef<HTMLDivElement>(null);
const imageDiffRef = React.useRef<HTMLDivElement>(null);
const [scrolled, setScrolled] = React.useState(false);
React.useEffect(() => {
if (scrolled)
return;
setScrolled(true);
if (anchor === 'video')
videoRef.current?.scrollIntoView({ block: 'start', inline: 'start' });
if (anchor === 'diff')
imageDiffRef.current?.scrollIntoView({ block: 'start', inline: 'start' });
}, [scrolled, anchor, setScrolled, videoRef]);
return <div className='test-result'>
{!!errors.length && <AutoChip header='Errors'>
{errors.map((error, index) => {
@ -103,49 +94,52 @@ export const TestResultView: React.FC<{
})}
</AutoChip>}
{!!result.steps.length && <AutoChip header='Test Steps'>
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} depth={0}></StepTreeItem>)}
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} result={result} test={test} depth={0}/>)}
</AutoChip>}
{diffs.map((diff, index) =>
<AutoChip key={`diff-${index}`} dataTestId='test-results-image-diff' header={`Image mismatch: ${diff.name}`} targetRef={imageDiffRef}>
<ImageDiffView key='image-diff' diff={diff}></ImageDiffView>
</AutoChip>
<Anchor key={`diff-${index}`} id={diff.anchors}>
<AutoChip dataTestId='test-results-image-diff' header={`Image mismatch: ${diff.name}`} revealOnAnchorId={diff.anchors}>
<ImageDiffView diff={diff}/>
</AutoChip>
</Anchor>
)}
{!!screenshots.length && <AutoChip header='Screenshots'>
{!!screenshots.length && <AutoChip header='Screenshots' revealOnAnchorId={screenshotAnchors}>
{screenshots.map((a, i) => {
return <div key={`screenshot-${i}`}>
return <Anchor key={`screenshot-${i}`} id={`attachment-${a.name}`}>
<a href={a.path}>
<img className='screenshot' src={a.path} />
</a>
<AttachmentLink attachment={a}></AttachmentLink>
</div>;
</Anchor>;
})}
</AutoChip>}
{!!traces.length && <AutoChip header='Traces'>
{!!traces.length && <Anchor id='attachment-trace'><AutoChip header='Traces' revealOnAnchorId='attachment-trace'>
{<div>
<a href={generateTraceUrl(traces)}>
<img className='screenshot' src={traceImage} style={{ width: 192, height: 117, marginLeft: 20 }} />
</a>
{traces.map((a, i) => <AttachmentLink key={`trace-${i}`} attachment={a} linkName={traces.length === 1 ? 'trace' : `trace-${i + 1}`}></AttachmentLink>)}
</div>}
</AutoChip>}
</AutoChip></Anchor>}
{!!videos.length && <AutoChip header='Videos' targetRef={videoRef}>
{!!videos.length && <Anchor id='attachment-video'><AutoChip header='Videos' revealOnAnchorId='attachment-video'>
{videos.map((a, i) => <div key={`video-${i}`}>
<video controls>
<source src={a.path} type={a.contentType}/>
</video>
<AttachmentLink attachment={a}></AttachmentLink>
</div>)}
</AutoChip>}
</AutoChip></Anchor>}
{!!(otherAttachments.size + htmls.length) && <AutoChip header='Attachments'>
{[...htmls].map((a, i) => (
<AttachmentLink key={`html-link-${i}`} attachment={a} openInNewTab />)
{!!otherAttachments.size && <AutoChip header='Attachments' revealOnAnchorId={otherAttachmentAnchors}>
{[...otherAttachments].map((a, i) =>
<Anchor key={`attachment-link-${i}`} id={`attachment-${a.name}`}>
<AttachmentLink attachment={a} openInNewTab={a.contentType.startsWith('text/html')} />
</Anchor>
)}
{[...otherAttachments].map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
</AutoChip>}
</div>;
};
@ -175,19 +169,23 @@ function classifyErrors(testErrors: string[], diffs: ImageDiff[]) {
}
const StepTreeItem: React.FC<{
test: TestCase;
result: TestResult;
step: TestStep;
depth: number,
}> = ({ step, depth }) => {
return <TreeItem title={<span>
}> = ({ test, step, result, depth }) => {
const attachmentName = step.title.match(/^attach "(.*)"$/)?.[1];
return <TreeItem title={<span aria-label={step.title}>
<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
{attachmentName && <a style={{ float: 'right' }} title='link to attachment' href={testResultHref({ test, result, anchor: `attachment-${attachmentName}` })} onClick={evt => { evt.stopPropagation(); }}>{icons.attachment()}</a>}
{statusIcon(step.error || step.duration === -1 ? 'failed' : 'passed')}
<span>{step.title}</span>
{step.count > 1 && <> <span className='test-result-counter'>{step.count}</span></>}
{step.location && <span className='test-result-path'> {step.location.file}:{step.location.line}</span>}
</span>} loadChildren={step.steps.length + (step.snippet ? 1 : 0) ? () => {
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>);
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} />);
if (step.snippet)
children.unshift(<TestErrorView testId='test-snippet' key='line' error={step.snippet}></TestErrorView>);
children.unshift(<TestErrorView testId='test-snippet' key='line' error={step.snippet}/>);
return children;
} : undefined} depth={depth}></TreeItem>;
} : undefined} depth={depth}/>;
};

View file

@ -25,6 +25,11 @@
cursor: pointer;
}
.tree-item-title.selected {
text-decoration: underline var(--color-underlinenav-icon);
text-decoration-thickness: 1.5px;
}
.tree-item-body {
min-height: 18px;
}

View file

@ -17,6 +17,7 @@
import * as React from 'react';
import './treeItem.css';
import * as icons from './icons';
import { clsx } from '@web/uiUtils';
export const TreeItem: React.FunctionComponent<{
title: JSX.Element,
@ -28,9 +29,8 @@ export const TreeItem: React.FunctionComponent<{
style?: React.CSSProperties,
}> = ({ title, loadChildren, onClick, expandByDefault, depth, selected, style }) => {
const [expanded, setExpanded] = React.useState(expandByDefault || false);
const className = selected ? 'tree-item-title selected' : 'tree-item-title';
return <div className={'tree-item'} style={style}>
<span className={className} style={{ whiteSpace: 'nowrap', paddingLeft: depth * 22 + 4 }} onClick={() => { onClick?.(); setExpanded(!expanded); }} >
<span className={clsx('tree-item-title', selected && 'selected')} style={{ whiteSpace: 'nowrap', paddingLeft: depth * 22 + 4 }} onClick={() => { onClick?.(); setExpanded(!expanded); }} >
{loadChildren && !!expanded && icons.downArrow()}
{loadChildren && !expanded && icons.rightArrow()}
{!loadChildren && <span style={{ visibility: 'hidden' }}>{icons.rightArrow()}</span>}

View file

@ -24,4 +24,4 @@ try {
}
if (install)
install(['chromium', 'ffmpeg']);
install(['chromium', 'chromium-headless-shell', 'ffmpeg']);

View file

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

View file

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

View file

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

View file

@ -24,4 +24,4 @@ try {
}
if (install)
install(['chromium', 'ffmpeg']);
install(['chromium', 'chromium-headless-shell', 'ffmpeg']);

View file

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

View file

@ -6,17 +6,17 @@ This project incorporates components from the projects listed below. The origina
- @types/node@17.0.24 (https://github.com/DefinitelyTyped/DefinitelyTyped)
- @types/yauzl@2.10.0 (https://github.com/DefinitelyTyped/DefinitelyTyped)
- agent-base@7.1.1 (https://github.com/TooTallNate/proxy-agents)
- agent-base@7.1.3 (https://github.com/TooTallNate/proxy-agents)
- balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match)
- brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion)
- buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32)
- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror)
- codemirror@5.65.18 (https://github.com/codemirror/CodeMirror)
- colors@1.4.0 (https://github.com/Marak/colors.js)
- commander@8.3.0 (https://github.com/tj/commander.js)
- concat-map@0.0.1 (https://github.com/substack/node-concat-map)
- debug@4.3.4 (https://github.com/debug-js/debug)
- define-lazy-prop@2.0.0 (https://github.com/sindresorhus/define-lazy-prop)
- diff-match-patch@1.0.5 (https://github.com/JackuB/diff-match-patch)
- diff@7.0.0 (https://github.com/kpdecker/jsdiff)
- dotenv@16.4.5 (https://github.com/motdotla/dotenv)
- end-of-stream@1.4.4 (https://github.com/mafintosh/end-of-stream)
- escape-string-regexp@2.0.0 (https://github.com/sindresorhus/escape-string-regexp)
@ -24,7 +24,7 @@ This project incorporates components from the projects listed below. The origina
- fd-slicer@1.1.0 (https://github.com/andrewrk/node-fd-slicer)
- get-stream@5.2.0 (https://github.com/sindresorhus/get-stream)
- graceful-fs@4.2.10 (https://github.com/isaacs/node-graceful-fs)
- https-proxy-agent@7.0.5 (https://github.com/TooTallNate/proxy-agents)
- https-proxy-agent@7.0.6 (https://github.com/TooTallNate/proxy-agents)
- ip-address@9.0.5 (https://github.com/beaugunderson/ip-address)
- is-docker@2.2.1 (https://github.com/sindresorhus/is-docker)
- is-wsl@2.2.0 (https://github.com/sindresorhus/is-wsl)
@ -43,7 +43,7 @@ This project incorporates components from the projects listed below. The origina
- retry@0.12.0 (https://github.com/tim-kos/node-retry)
- signal-exit@3.0.7 (https://github.com/tapjs/signal-exit)
- smart-buffer@4.2.0 (https://github.com/JoshGlazebrook/smart-buffer)
- socks-proxy-agent@8.0.4 (https://github.com/TooTallNate/proxy-agents)
- socks-proxy-agent@8.0.5 (https://github.com/TooTallNate/proxy-agents)
- socks@2.8.3 (https://github.com/JoshGlazebrook/socks)
- sprintf-js@1.1.3 (https://github.com/alexei/sprintf.js)
- stack-utils@2.0.5 (https://github.com/tapjs/stack-utils)
@ -105,7 +105,7 @@ MIT License
=========================================
END OF @types/yauzl@2.10.0 AND INFORMATION
%% agent-base@7.1.1 NOTICES AND INFORMATION BEGIN HERE
%% agent-base@7.1.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
@ -130,7 +130,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF agent-base@7.1.1 AND INFORMATION
END OF agent-base@7.1.3 AND INFORMATION
%% balanced-match@1.0.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -208,7 +208,7 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL
=========================================
END OF buffer-crc32@0.2.13 AND INFORMATION
%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE
%% codemirror@5.65.18 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
@ -232,7 +232,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF codemirror-shadow-1@0.0.1 AND INFORMATION
END OF codemirror@5.65.18 AND INFORMATION
%% colors@1.4.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -352,211 +352,39 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
=========================================
END OF define-lazy-prop@2.0.0 AND INFORMATION
%% diff-match-patch@1.0.5 NOTICES AND INFORMATION BEGIN HERE
%% diff@7.0.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
BSD 3-Clause License
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
All rights reserved.
1. Definitions.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=========================================
END OF diff-match-patch@1.0.5 AND INFORMATION
END OF diff@7.0.0 AND INFORMATION
%% dotenv@16.4.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -714,7 +542,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
=========================================
END OF graceful-fs@4.2.10 AND INFORMATION
%% https-proxy-agent@7.0.5 NOTICES AND INFORMATION BEGIN HERE
%% https-proxy-agent@7.0.6 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
@ -739,7 +567,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF https-proxy-agent@7.0.5 AND INFORMATION
END OF https-proxy-agent@7.0.6 AND INFORMATION
%% ip-address@9.0.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
@ -1177,7 +1005,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF smart-buffer@4.2.0 AND INFORMATION
%% socks-proxy-agent@8.0.4 NOTICES AND INFORMATION BEGIN HERE
%% socks-proxy-agent@8.0.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
@ -1202,7 +1030,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF socks-proxy-agent@8.0.4 AND INFORMATION
END OF socks-proxy-agent@8.0.5 AND INFORMATION
%% socks@2.8.3 NOTICES AND INFORMATION BEGIN HERE
=========================================

View file

@ -1,2 +0,0 @@
See building instructions at [`/browser_patches/winldd/README.md`](../../../browser_patches/winldd/README.md)

View file

@ -7,15 +7,17 @@ if [[ $(arch) == "aarch64" ]]; then
exit 1
fi
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old beta if any.

View file

@ -7,15 +7,17 @@ if [[ $(arch) == "aarch64" ]]; then
exit 1
fi
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old stable if any.

View file

@ -8,15 +8,17 @@ if [[ $(arch) == "aarch64" ]]; then
exit 1
fi
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old beta if any.

View file

@ -8,15 +8,17 @@ if [[ $(arch) == "aarch64" ]]; then
exit 1
fi
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old dev if any.

View file

@ -8,15 +8,17 @@ if [[ $(arch) == "aarch64" ]]; then
exit 1
fi
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old stable if any.

View file

@ -3,33 +3,35 @@
"browsers": [
{
"name": "chromium",
"revision": "1145",
"revision": "1152",
"installByDefault": true,
"browserVersion": "131.0.6778.13"
"browserVersion": "132.0.6834.46"
},
{
"name": "chromium-tip-of-tree",
"revision": "1271",
"revision": "1288",
"installByDefault": false,
"browserVersion": "132.0.6791.0"
"browserVersion": "133.0.6905.0"
},
{
"name": "firefox",
"revision": "1465",
"revision": "1466",
"installByDefault": true,
"browserVersion": "131.0"
"browserVersion": "132.0"
},
{
"name": "firefox-beta",
"revision": "1465",
"revision": "1466",
"installByDefault": false,
"browserVersion": "132.0b8"
"browserVersion": "133.0b9"
},
{
"name": "webkit",
"revision": "2098",
"revision": "2120",
"installByDefault": true,
"revisionOverrides": {
"debian11-x64": "2105",
"debian11-arm64": "2105",
"mac10.14": "1446",
"mac10.15": "1616",
"mac11": "1816",
@ -39,17 +41,22 @@
"ubuntu20.04-x64": "2092",
"ubuntu20.04-arm64": "2092"
},
"browserVersion": "18.0"
"browserVersion": "18.2"
},
{
"name": "ffmpeg",
"revision": "1010",
"revision": "1011",
"installByDefault": true,
"revisionOverrides": {
"mac12": "1010",
"mac12-arm64": "1010"
}
},
{
"name": "winldd",
"revision": "1007",
"installByDefault": false
},
{
"name": "android",
"revision": "1001",

View file

@ -11,10 +11,10 @@
"colors": "1.4.0",
"commander": "8.3.0",
"debug": "^4.3.4",
"diff-match-patch": "^1.0.5",
"diff": "^7.0.0",
"dotenv": "^16.4.5",
"graceful-fs": "4.2.10",
"https-proxy-agent": "7.0.5",
"https-proxy-agent": "7.0.6",
"jpeg-js": "0.4.4",
"mime": "^3.0.0",
"minimatch": "^3.1.2",
@ -24,14 +24,14 @@
"proxy-from-env": "1.1.0",
"retry": "0.12.0",
"signal-exit": "3.0.7",
"socks-proxy-agent": "8.0.4",
"socks-proxy-agent": "8.0.5",
"stack-utils": "2.0.5",
"ws": "8.17.1",
"yaml": "^2.5.1"
"yaml": "^2.6.0"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/diff-match-patch": "^1.0.36",
"@types/diff": "^6.0.0",
"@types/mime": "^2.0.3",
"@types/minimatch": "^3.0.5",
"@types/pngjs": "^6.0.1",
@ -51,11 +51,12 @@
"@types/ms": "*"
}
},
"node_modules/@types/diff-match-patch": {
"version": "1.0.36",
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
"dev": true
"node_modules/@types/diff": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-6.0.0.tgz",
"integrity": "sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "2.0.3",
@ -139,13 +140,10 @@
}
},
"node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
@ -209,10 +207,14 @@
"node": ">=8"
}
},
"node_modules/diff-match-patch": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
"node_modules/diff": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
"integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dotenv": {
"version": "16.4.5",
@ -239,12 +241,12 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
"node_modules/https-proxy-agent": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.0.2",
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
@ -398,12 +400,12 @@
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz",
"integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==",
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.1",
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
@ -469,10 +471,10 @@
"@types/ms": "*"
}
},
"@types/diff-match-patch": {
"version": "1.0.36",
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
"@types/diff": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-6.0.0.tgz",
"integrity": "sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==",
"dev": true
},
"@types/mime": {
@ -557,12 +559,9 @@
}
},
"agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"requires": {
"debug": "^4.3.4"
}
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="
},
"balanced-match": {
"version": "1.0.2",
@ -606,10 +605,10 @@
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="
},
"diff-match-patch": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
"diff": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
"integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="
},
"dotenv": {
"version": "16.4.5",
@ -627,11 +626,11 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
},
"https-proxy-agent": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"requires": {
"agent-base": "^7.0.2",
"agent-base": "^7.1.2",
"debug": "4"
}
},
@ -735,11 +734,11 @@
}
},
"socks-proxy-agent": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz",
"integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==",
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"requires": {
"agent-base": "^7.1.1",
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
}

View file

@ -12,10 +12,10 @@
"colors": "1.4.0",
"commander": "8.3.0",
"debug": "^4.3.4",
"diff-match-patch": "^1.0.5",
"diff": "^7.0.0",
"dotenv": "^16.4.5",
"graceful-fs": "4.2.10",
"https-proxy-agent": "7.0.5",
"https-proxy-agent": "7.0.6",
"jpeg-js": "0.4.4",
"mime": "^3.0.0",
"minimatch": "^3.1.2",
@ -25,14 +25,14 @@
"proxy-from-env": "1.1.0",
"retry": "0.12.0",
"signal-exit": "3.0.7",
"socks-proxy-agent": "8.0.4",
"socks-proxy-agent": "8.0.5",
"stack-utils": "2.0.5",
"ws": "8.17.1",
"yaml": "^2.5.1"
"yaml": "^2.6.0"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/diff-match-patch": "^1.0.36",
"@types/diff": "^6.0.0",
"@types/mime": "^2.0.3",
"@types/minimatch": "^3.0.5",
"@types/pngjs": "^6.0.1",

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