diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b25a131d44..cb06a17e77 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,92 +1,77 @@
# Contributing
-- [How to Contribute](#how-to-contribute)
- * [Getting Code](#getting-code)
- * [Code reviews](#code-reviews)
- * [Code Style](#code-style)
- * [API guidelines](#api-guidelines)
- * [Commit Messages](#commit-messages)
- * [Writing Documentation](#writing-documentation)
- * [Adding New Dependencies](#adding-new-dependencies)
- * [Running & Writing Tests](#running--writing-tests)
- * [Public API Coverage](#public-api-coverage)
-- [Contributor License Agreement](#contributor-license-agreement)
- * [Code of Conduct](#code-of-conduct)
+## Choose an issue
-## How to Contribute
+Playwright **requires an issue** for every contribution, except for minor documentation updates. We strongly recommend to pick an issue labeled `open-to-a-pull-request` for your first contribution to the project.
-We strongly recommend that you open an issue before beginning any code modifications. This is particularly important if the changes involve complex logic or if the existing code isn't immediately clear. By doing so, we can discuss and agree upon the best approach to address a bug or implement a feature, ensuring that our efforts are aligned.
+If you are passioned about a bug/feature, but cannot find an issue describing it, **file an issue first**. This will facilitate the discussion and you might get some early feedback from project maintainers before spending your time on creating a pull request.
-### Getting Code
-
-Make sure you're running Node.js 20 to verify and upgrade NPM do:
+## Make a change
+Make sure you're running Node.js 20 or later.
```bash
node --version
-npm --version
-npm i -g npm@latest
```
-1. Clone this repository
-
- ```bash
- git clone https://github.com/microsoft/playwright
- cd playwright
- ```
-
-2. Install dependencies
-
- ```bash
- npm ci
- ```
-
-3. Build Playwright
-
- ```bash
- npm run build
- ```
-
-4. Run tests
-
- This will run a test on line `23` in `page-fill.spec.ts`:
-
- ```bash
- npm run ctest -- page-fill:23
- ```
-
- See [here](#running--writing-tests) for more information about running and writing tests.
-
-### Code reviews
-
-All submissions, including submissions by project members, require review. We
-use GitHub pull requests for this purpose. Consult
-[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
-information on using pull requests.
-
-### Code Style
-
-- Coding style is fully defined in [.eslintrc](https://github.com/microsoft/playwright/blob/main/.eslintrc.js)
-- Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.
-
-To run code linter, use:
-
+Clone the repository. If you plan to send a pull request, it might be better to [fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) first.
```bash
-npm run eslint
+git clone https://github.com/microsoft/playwright
+cd playwright
```
-### API guidelines
+Install dependencies and run the build in watch mode.
+```bash
+npm ci
+npm run watch
+npx playwright install
+```
-When authoring new API methods, consider the following:
+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).
-- Expose as little information as needed. When in doubt, donβt expose new information.
-- Methods are used in favor of getters/setters.
- - The only exception is namespaces, e.g. `page.keyboard` and `page.coverage`
-- All string literals must be lowercase. This includes event names and option values.
-- Avoid adding "sugar" API (API that is trivially implementable in user-space) unless they're **very** common.
+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).
-### Commit Messages
+Coding style is fully defined in [.eslintrc](https://github.com/microsoft/playwright/blob/main/.eslintrc.js). Before creating a pull request, or at any moment during development, run linter to check all kinds of things:
+ ```bash
+ npm run lint
+ ```
-Commit messages should follow the Semantic Commit Messages format:
+Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory.
+
+### Write documentation
+
+Every part of the public API should be documented in [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src), in the same change that adds/changes the API. We use markdown files with custom structure to specify the API. Take a look around for an example.
+
+Various other files are generated from the API specification. If you are running `npm run watch`, these will be re-generated automatically.
+
+Larger changes will require updates to the documentation guides as well. This will be made clear during the code review.
+
+## Add a test
+
+Playwright requires a test for almost any new or modified functionality. An exception would be a pure refactoring, but chances are you are doing more than that.
+
+There are multiple [test suites](https://github.com/microsoft/playwright/blob/main/tests) in Playwright that will be executed on the CI. The two most important that you need to run locally are:
+
+- Library tests cover APIs not related to the test runner.
+ ```bash
+ # fast path runs all tests in Chromium
+ npm run ctest
+
+ # slow path runs all tests in three browsers
+ npm run test
+ ```
+
+- Test runner tests.
+ ```bash
+ npm run ttest
+ ```
+
+Since Playwright tests are using Playwright under the hood, everything from our documentation applies, for example [this guide on running and debugging tests](https://playwright.dev/docs/running-tests#running-tests).
+
+Note that tests should be *hermetic*, and not depend on external services. Tests should work on all three platforms: macOS, Linux and Windows.
+
+## Write a commit message
+
+Commit messages should follow the [Semantic Commit Messages](https://www.conventionalcommits.org/en/v1.0.0/) format:
```
label(namespace): title
@@ -97,131 +82,57 @@ footer
```
1. *label* is one of the following:
- - `fix` - playwright bug fixes.
- - `feat` - playwright features.
- - `docs` - changes to docs, e.g. `docs(api): ..` to change documentation.
- - `test` - changes to playwright tests infrastructure.
- - `devops` - build-related work, e.g. CI related patches and general changes to the browser build infrastructure
+ - `fix` - bug fixes
+ - `feat` - new features
+ - `docs` - documentation-only changes
+ - `test` - test-only changes
+ - `devops` - changes to the CI or build
- `chore` - everything that doesn't fall under previous categories
-2. *namespace* is put in parenthesis after label and is optional. Must be lowercase.
-3. *title* is a brief summary of changes.
-4. *description* is **optional**, new-line separated from title and is in present tense.
-5. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues.
+1. *namespace* is put in parenthesis after label and is optional. Must be lowercase.
+1. *title* is a brief summary of changes.
+1. *description* is **optional**, new-line separated from title and is in present tense.
+1. *footer* is **optional**, new-line separated from *description* and contains "fixes" / "references" attribution to github issues.
Example:
```
-fix(firefox): make sure session cookies work
+feat(trace viewer): network panel filtering
-This patch fixes session cookies in the firefox browser.
+This patch adds a filtering toolbar to the network panel.
+
-Fixes #123, fixes #234
+Fixes #123, references #234.
```
-### Writing Documentation
+## Send a pull request
-All API classes, methods, and events should have a description in [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src). There's a [documentation linter](https://github.com/microsoft/playwright/tree/main/utils/doclint) which makes sure documentation is aligned with the codebase.
+All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests.
-To run the documentation linter, use:
+After a successful code review, one of the maintainers will merge your pull request. Congratulations!
+## More details
+
+**No new dependencies**
+
+There is a very high bar for new dependencies, including updating to a new version of an existing dependency. We recommend to explicitly discuss this in an issue and get a green light from a maintainer, before creating a pull request that updates dependencies.
+
+**Custom browser build**
+
+To run tests with custom browser executable, specify `CRPATH`, `WKPATH` or `FFPATH` env variable that points to browser executable:
```bash
-npm run doc
+CRPATH= npm run ctest
```
-To build the documentation site locally and test how your changes will look in practice:
+You will also find `DEBUG=pw:browser` useful for debugging custom builds.
-1. Clone the [microsoft/playwright.dev](https://github.com/microsoft/playwright.dev) repo
-1. Follow [the playwright.dev README instructions to "roll docs"](https://github.com/microsoft/playwright.dev/#roll-docs) against your local `playwright` repo with your changes in progress
-1. Follow [the playwright.dev README instructions to "run dev server"](https://github.com/microsoft/playwright.dev/#run-dev-server) to view your changes
+**Building documentation site**
-### Adding New Dependencies
+The [playwright.dev](https://playwright.dev/) documentation site lives in a separate repository, and documentation from [`docs/src`](https://github.com/microsoft/playwright/blob/main/docs/src) is frequently rolled there.
-For all dependencies (both installation and development):
-- **Do not add** a dependency if the desired functionality is easily implementable.
-- If adding a dependency, it should be well-maintained and trustworthy.
-
-A barrier for introducing new installation dependencies is especially high:
-- **Do not add** installation dependency unless it's critical to project success.
-
-### Running & Writing Tests
-
-- Every feature should be accompanied by a test.
-- Every public api event/method should be accompanied by a test.
-- Tests should be *hermetic*. Tests should not depend on external services.
-- Tests should work on all three platforms: Mac, Linux and Win. This is especially important for screenshot tests.
-
-Playwright tests are located in [`tests`](https://github.com/microsoft/playwright/blob/main/tests) and use `@playwright/test` test runner.
-These are integration tests, making sure public API methods and events work as expected.
-
-- To run all tests:
-
- ```bash
- npx playwright install
- npm run test
- ```
-
- Be sure to run `npm run build` or let `npm run watch` run before you re-run the
- tests after making your changes to check them.
-
-- To run tests in Chromium
-
- ```bash
- npm run ctest # also `ftest` for firefox and `wtest` for WebKit
- npm run ctest -- page-fill:23 # runs line 23 of page-fill.spec.ts
- ```
-
-- To run tests in WebKit / Firefox, use `wtest` or `ftest`.
-
-- To run the Playwright test runner tests
-
- ```bash
- npm run ttest
- npm run ttest -- --grep "specific test"
- ```
-
-- To run a specific test, substitute `it` with `it.only`, or use the `--grep 'My test'` CLI parameter:
-
- ```js
- ...
- // Using "it.only" to run a specific test
- it.only('should work', async ({server, page}) => {
- const response = await page.goto(server.EMPTY_PAGE);
- expect(response.ok).toBe(true);
- });
- // or
- playwright test --config=xxx --grep 'should work'
- ```
-
-- To disable a specific test, substitute `it` with `it.skip`:
-
- ```js
- ...
- // Using "it.skip" to skip a specific test
- it.skip('should work', async ({server, page}) => {
- const response = await page.goto(server.EMPTY_PAGE);
- expect(response.ok).toBe(true);
- });
- ```
-
-- To run tests in non-headless (headed) mode:
-
- ```bash
- npm run ctest -- --headed
- ```
-
-- To run tests with custom browser executable, specify `CRPATH`, `WKPATH` or `FFPATH` env variable that points to browser executable:
-
- ```bash
- CRPATH= npm run ctest
- ```
-
-- When should a test be marked with `skip` or `fixme`?
-
- - **`skip(condition)`**: This test *should ***never*** work* for `condition`
- where `condition` is usually something like: `test.skip(browserName === 'chromium', 'This does not work because of ...')`.
-
- - **`fixme(condition)`**: This test *should ***eventually*** work* for `condition`
- where `condition` is usually something like: `test.fixme(browserName === 'chromium', 'We are waiting for version x')`.
+Most of the time this should not concern you. However, if you are doing something unusual in the docs, you can build locally and test how your changes will look in practice:
+1. Clone the [microsoft/playwright.dev](https://github.com/microsoft/playwright.dev) repo.
+1. Follow [the playwright.dev README instructions to "roll docs"](https://github.com/microsoft/playwright.dev/#roll-docs) against your local `playwright` repo with your changes in progress.
+1. Follow [the playwright.dev README instructions to "run dev server"](https://github.com/microsoft/playwright.dev/#run-dev-server) to view your changes.
## Contributor License Agreement
diff --git a/README.md b/README.md
index e865883de9..860e11db65 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,6 @@ npx playwright install
You can optionally install only selected browsers, see [install browsers](https://playwright.dev/docs/cli#install-browsers) for more details. Or you can install no browsers at all and use existing [browser channels](https://playwright.dev/docs/browsers).
* [Getting started](https://playwright.dev/docs/intro)
-* [Installation configuration](https://playwright.dev/docs/installation)
* [API reference](https://playwright.dev/docs/api/class-playwright)
## Capabilities
@@ -163,7 +162,7 @@ test('Intercept network requests', async ({ page }) => {
## Resources
-* [Documentation](https://playwright.dev/docs/intro)
+* [Documentation](https://playwright.dev)
* [API reference](https://playwright.dev/docs/api/class-playwright/)
* [Contribution guide](CONTRIBUTING.md)
* [Changelog](https://github.com/microsoft/playwright/releases)
diff --git a/docs/src/locators.md b/docs/src/locators.md
index 648a654177..c3a2817670 100644
--- a/docs/src/locators.md
+++ b/docs/src/locators.md
@@ -62,11 +62,11 @@ expect(page.get_by_text("Welcome, John!")).to_be_visible()
```
```csharp
-await page.GetByLabel("User Name").FillAsync("John");
+await Page.GetByLabel("User Name").FillAsync("John");
-await page.GetByLabel("Password").FillAsync("secret-password");
+await Page.GetByLabel("Password").FillAsync("secret-password");
-await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
+await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync();
```
@@ -101,7 +101,7 @@ page.get_by_role("button", name="Sign in").click()
```
```csharp
-await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
+await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
```
:::note
@@ -143,7 +143,7 @@ locator.click()
```
```csharp
-var locator = page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
+var locator = Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
await locator.HoverAsync();
await locator.ClickAsync();
@@ -180,7 +180,7 @@ locator.click()
```
```csharp
-var locator = page
+var locator = Page
.FrameLocator("#my-frame")
.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
@@ -249,11 +249,11 @@ await Expect(Page
.GetByRole(AriaRole.Heading, new() { Name = "Sign up" }))
.ToBeVisibleAsync();
-await page
+await Page
.GetByRole(AriaRole.Checkbox, new() { Name = "Subscribe" })
.CheckAsync();
-await page
+await Page
.GetByRole(AriaRole.Button, new() {
NameRegex = new Regex("submit", RegexOptions.IgnoreCase)
})
@@ -298,7 +298,7 @@ page.get_by_label("Password").fill("secret")
```
```csharp
-await page.GetByLabel("Password").FillAsync("secret");
+await Page.GetByLabel("Password").FillAsync("secret");
```
:::note[When to use label locators]
@@ -335,7 +335,7 @@ page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
```
```csharp
-await page
+await Page
.GetByPlaceholder("name@example.com")
.FillAsync("playwright@microsoft.com");
```
@@ -468,7 +468,7 @@ page.get_by_alt_text("playwright logo").click()
```
```csharp
-await page.GetByAltText("playwright logo").ClickAsync();
+await Page.GetByAltText("playwright logo").ClickAsync();
```
:::note[When to use alt locators]
@@ -540,7 +540,7 @@ page.get_by_test_id("directions").click()
```
```csharp
-await page.GetByTestId("directions").ClickAsync();
+await Page.GetByTestId("directions").ClickAsync();
```
:::note[When to use testid locators]
@@ -604,7 +604,7 @@ page.get_by_test_id("directions").click()
```
```csharp
-await page.GetByTestId("directions").ClickAsync();
+await Page.GetByTestId("directions").ClickAsync();
```
### Locate by CSS or XPath
@@ -644,11 +644,11 @@ page.locator("//button").click()
```
```csharp
-await page.Locator("css=button").ClickAsync();
-await page.Locator("xpath=//button").ClickAsync();
+await Page.Locator("css=button").ClickAsync();
+await Page.Locator("xpath=//button").ClickAsync();
-await page.Locator("button").ClickAsync();
-await page.Locator("//button").ClickAsync();
+await Page.Locator("button").ClickAsync();
+await Page.Locator("//button").ClickAsync();
```
XPath and CSS selectors can be tied to the DOM structure or implementation. These selectors can break when the DOM structure changes. Long CSS or XPath chains below are an example of a **bad practice** that leads to unstable tests:
@@ -688,9 +688,9 @@ page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
```
```csharp
-await page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
+await Page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
-await page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
+await Page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
```
:::note[When to use this]
diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md
index b366c43dbd..f6bd430163 100644
--- a/docs/src/release-notes-js.md
+++ b/docs/src/release-notes-js.md
@@ -8,6 +8,11 @@ import LiteYouTube from '@site/src/components/LiteYouTube';
## Version 1.48
+
+
### WebSocket routing
New methods [`method: Page.routeWebSocket`] and [`method: BrowserContext.routeWebSocket`] allow to intercept, modify and mock WebSocket connections initiated in the page. Below is a simple example that mocks WebSocket communication by responding to a `"request"` with a `"response"`.
diff --git a/docs/src/test-api/class-test.md b/docs/src/test-api/class-test.md
index 31f60b7e9c..7ea05d4c56 100644
--- a/docs/src/test-api/class-test.md
+++ b/docs/src/test-api/class-test.md
@@ -1138,6 +1138,57 @@ Optional description that will be reflected in a test report.
+## method: Test.fail.only
+* since: v1.49
+
+You can use `test.fail.only` to focus on a specific test that is expected to fail. This is particularly useful when debugging a failing test or working on a specific issue.
+
+To declare a focused "failing" test:
+* `test.fail.only(title, body)`
+* `test.fail.only(title, details, body)`
+
+**Usage**
+
+You can declare a focused failing test, so that Playwright runs only this test and ensures it actually fails.
+
+```js
+import { test, expect } from '@playwright/test';
+
+test.fail.only('focused failing test', async ({ page }) => {
+ // This test is expected to fail
+});
+test('not in the focused group', async ({ page }) => {
+ // This test will not run
+});
+```
+
+### param: Test.fail.only.title
+* since: v1.49
+
+- `title` ?<[string]>
+
+Test title.
+
+### param: Test.fail.only.details
+* since: v1.49
+
+- `details` ?<[Object]>
+ - `tag` ?<[string]|[Array]<[string]>>
+ - `annotation` ?<[Object]|[Array]<[Object]>>
+ - `type` <[string]>
+ - `description` ?<[string]>
+
+See [`method: Test.describe`] for test details description.
+
+### param: Test.fail.only.body
+* since: v1.49
+
+- `body` ?<[function]\([Fixtures], [TestInfo]\)>
+
+Test body that takes one or two arguments: an object with fixtures and optional [TestInfo].
+
+
+
## method: Test.fixme
* since: v1.10
diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md
index 0d1f4c1538..cd70b21b70 100644
--- a/docs/src/test-api/class-testconfig.md
+++ b/docs/src/test-api/class-testconfig.md
@@ -110,9 +110,9 @@ export default defineConfig({
## property: TestConfig.globalSetup
* since: v1.10
-- type: ?<[string]>
+- type: ?<[string]|[Array]<[string]>>
-Path to the global setup file. This file will be required and run before all the tests. It must export a single function that takes a [FullConfig] argument.
+Path to the global setup file. This file will be required and run before all the tests. It must export a single function that takes a [FullConfig] argument. Pass an array of paths to specify multiple global setup files.
Learn more about [global setup and teardown](../test-global-setup-teardown.md).
@@ -128,9 +128,9 @@ export default defineConfig({
## property: TestConfig.globalTeardown
* since: v1.10
-- type: ?<[string]>
+- type: ?<[string]|[Array]<[string]>>
-Path to the global teardown file. This file will be required and run after all the tests. It must export a single function. See also [`property: TestConfig.globalSetup`].
+Path to the global teardown file. This file will be required and run after all the tests. It must export a single function. See also [`property: TestConfig.globalSetup`]. Pass an array of paths to specify multiple global teardown files.
Learn more about [global setup and teardown](../test-global-setup-teardown.md).
diff --git a/packages/html-reporter/src/testErrorView.tsx b/packages/html-reporter/src/testErrorView.tsx
index 520da1fc19..8d2bb13bd3 100644
--- a/packages/html-reporter/src/testErrorView.tsx
+++ b/packages/html-reporter/src/testErrorView.tsx
@@ -22,9 +22,10 @@ import { ImageDiffView } from '@web/shared/imageDiffView';
export const TestErrorView: React.FC<{
error: string;
-}> = ({ error }) => {
+ testId?: string;
+}> = ({ error, testId }) => {
const html = React.useMemo(() => ansiErrorToHtml(error), [error]);
- return ;
+ return ;
};
export const TestScreenshotErrorView: React.FC<{
diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx
index a3c75f30be..0096040d7c 100644
--- a/packages/html-reporter/src/testResultView.tsx
+++ b/packages/html-reporter/src/testResultView.tsx
@@ -215,7 +215,7 @@ const StepTreeItem: React.FC<{
return ;
}));
if (step.snippet)
- children.unshift();
+ children.unshift();
return children;
} : undefined} depth={depth}>;
};
diff --git a/packages/playwright-core/src/server/ariaSnapshot.ts b/packages/playwright-core/src/server/ariaSnapshot.ts
index 6f89dd21cf..e450e5b15d 100644
--- a/packages/playwright-core/src/server/ariaSnapshot.ts
+++ b/packages/playwright-core/src/server/ariaSnapshot.ts
@@ -40,8 +40,12 @@ export function parseAriaSnapshot(text: string): AriaTemplateNode {
return { role };
};
+ const normalizeWhitespace = (text: string) => {
+ return text.replace(/[\r\n\s\t]+/g, ' ').trim();
+ };
+
const valueOrRegex = (value: string): string | RegExp => {
- return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : value;
+ return value.startsWith('/') && value.endsWith('/') ? new RegExp(value.slice(1, -1)) : normalizeWhitespace(value);
};
const convert = (object: YamlNode | string): AriaTemplateNode | RegExp | string => {
diff --git a/packages/playwright-core/src/server/injected/ariaSnapshot.ts b/packages/playwright-core/src/server/injected/ariaSnapshot.ts
index 22ec9b5c42..907006ce0a 100644
--- a/packages/playwright-core/src/server/injected/ariaSnapshot.ts
+++ b/packages/playwright-core/src/server/injected/ariaSnapshot.ts
@@ -15,13 +15,13 @@
*/
import { escapeWithQuotes } from '@isomorphic/stringUtils';
-import { beginAriaCaches, endAriaCaches, getAriaRole, getElementAccessibleName, isElementIgnoredForAria } from './roleUtils';
-import { isElementVisible, isElementStyleVisibilityVisible } from './domUtils';
+import { accumulatedElementText, beginAriaCaches, endAriaCaches, getAriaRole, getElementAccessibleName, getPseudoContent, isElementIgnoredForAria } from './roleUtils';
+import { isElementVisible, isElementStyleVisibilityVisible, getElementComputedStyle } from './domUtils';
type AriaNode = {
role: string;
name?: string;
- children?: (AriaNode | string)[];
+ children: (AriaNode | string)[];
};
export type AriaTemplateNode = {
@@ -38,16 +38,20 @@ export function generateAriaTree(rootElement: Element): AriaNode {
const name = role ? getElementAccessibleName(element, false) || undefined : undefined;
const isLeaf = leafRoles.has(role);
- const result: AriaNode = { role, name };
- if (isLeaf && !name && element.textContent)
- result.children = [element.textContent];
+ const result: AriaNode = { role, name, children: [] };
+ if (isLeaf && !name) {
+ const text = accumulatedElementText(element);
+ if (text)
+ result.children = [text];
+ }
return { isLeaf, ariaNode: result };
};
const visit = (ariaNode: AriaNode, node: Node) => {
if (node.nodeType === Node.TEXT_NODE && node.nodeValue) {
- ariaNode.children = ariaNode.children || [];
- ariaNode.children.push(node.nodeValue);
+ const text = node.nodeValue;
+ if (text)
+ ariaNode.children.push(node.nodeValue || '');
return;
}
@@ -67,10 +71,8 @@ export function generateAriaTree(rootElement: Element): AriaNode {
if (visible) {
const childAriaNode = toAriaNode(element);
const isHiddenContainer = childAriaNode && hiddenContainerRoles.has(childAriaNode.ariaNode.role);
- if (childAriaNode && !isHiddenContainer) {
- ariaNode.children = ariaNode.children || [];
+ if (childAriaNode && !isHiddenContainer)
ariaNode.children.push(childAriaNode.ariaNode);
- }
if (isHiddenContainer || !childAriaNode?.isLeaf)
processChildNodes(childAriaNode?.ariaNode || ariaNode, element);
} else {
@@ -79,18 +81,36 @@ export function generateAriaTree(rootElement: Element): AriaNode {
};
function processChildNodes(ariaNode: AriaNode, element: Element) {
- // Process light DOM children
- for (let child = element.firstChild; child; child = child.nextSibling)
- visit(ariaNode, child);
- // Process shadow DOM children, if any
- if (element.shadowRoot) {
- for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
+ // Surround every element with spaces for the sake of concatenated text nodes.
+ const display = getElementComputedStyle(element)?.display || 'inline';
+ const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : '';
+ if (treatAsBlock)
+ ariaNode.children.push(treatAsBlock);
+
+ ariaNode.children.push(getPseudoContent(element, '::before'));
+ const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
+ if (assignedNodes.length) {
+ for (const child of assignedNodes)
visit(ariaNode, child);
+ } else {
+ for (let child = element.firstChild; child; child = child.nextSibling) {
+ if (!(child as Element | Text).assignedSlot)
+ visit(ariaNode, child);
+ }
+ if (element.shadowRoot) {
+ for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
+ visit(ariaNode, child);
+ }
}
+
+ ariaNode.children.push(getPseudoContent(element, '::after'));
+
+ if (treatAsBlock)
+ ariaNode.children.push(treatAsBlock);
}
beginAriaCaches();
- const ariaRoot: AriaNode = { role: '' };
+ const ariaRoot: AriaNode = { role: '', children: [] };
try {
visit(ariaRoot, rootElement);
} finally {
@@ -128,7 +148,7 @@ function normalizeStringChildren(rootA11yNode: AriaNode) {
}
}
flushChildren(buffer, normalizedChildren);
- ariaNode.children = normalizedChildren.length ? normalizedChildren : undefined;
+ ariaNode.children = normalizedChildren.length ? normalizedChildren : [];
};
visit(rootA11yNode);
}
@@ -144,7 +164,7 @@ const leafRoles = new Set([
'textbox', 'time', 'tooltip'
]);
-const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\n]+/g, ' ');
+const normalizeWhitespaceWithin = (text: string) => text.replace(/[\s\t\r\n]+/g, ' ');
function matchesText(text: string | undefined, template: RegExp | string | undefined) {
if (!template)
@@ -233,7 +253,7 @@ export function renderAriaTree(ariaNode: AriaNode): string {
lines.push(line);
return;
}
- lines.push(line + (ariaNode.children ? ':' : ''));
+ lines.push(line + (ariaNode.children.length ? ':' : ''));
for (const child of ariaNode.children || [])
visit(child, indent + ' ');
};
diff --git a/packages/playwright-core/src/server/injected/roleUtils.ts b/packages/playwright-core/src/server/injected/roleUtils.ts
index 6e05c39901..d085a8e36d 100644
--- a/packages/playwright-core/src/server/injected/roleUtils.ts
+++ b/packages/playwright-core/src/server/injected/roleUtils.ts
@@ -363,7 +363,7 @@ function queryInAriaOwned(element: Element, selector: string): Element[] {
return result;
}
-function getPseudoContent(element: Element, pseudo: '::before' | '::after') {
+export function getPseudoContent(element: Element, pseudo: '::before' | '::after') {
const cache = pseudo === '::before' ? cachePseudoContentBefore : cachePseudoContentAfter;
if (cache?.has(element))
return cache?.get(element) || '';
@@ -430,10 +430,6 @@ export function getElementAccessibleName(element: Element, includeHidden: boolea
accessibleName = asFlatString(getTextAlternativeInternal(element, {
includeHidden,
visitedElements: new Set(),
- embeddedInDescribedBy: undefined,
- embeddedInLabelledBy: undefined,
- embeddedInLabel: undefined,
- embeddedInNativeTextAlternative: undefined,
embeddedInTargetElement: 'self',
}));
}
@@ -458,10 +454,6 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
accessibleDescription = asFlatString(describedBy.map(ref => getTextAlternativeInternal(ref, {
includeHidden,
visitedElements: new Set(),
- embeddedInLabelledBy: undefined,
- embeddedInLabel: undefined,
- embeddedInNativeTextAlternative: undefined,
- embeddedInTargetElement: 'none',
embeddedInDescribedBy: { element: ref, hidden: isElementHiddenForAria(ref) },
})).join(' '));
} else if (element.hasAttribute('aria-description')) {
@@ -480,13 +472,13 @@ export function getElementAccessibleDescription(element: Element, includeHidden:
}
type AccessibleNameOptions = {
- includeHidden: boolean,
visitedElements: Set,
- embeddedInDescribedBy: { element: Element, hidden: boolean } | undefined,
- embeddedInLabelledBy: { element: Element, hidden: boolean } | undefined,
- embeddedInLabel: { element: Element, hidden: boolean } | undefined,
- embeddedInNativeTextAlternative: { element: Element, hidden: boolean } | undefined,
- embeddedInTargetElement: 'none' | 'self' | 'descendant',
+ includeHidden?: boolean,
+ embeddedInDescribedBy?: { element: Element, hidden: boolean },
+ embeddedInLabelledBy?: { element: Element, hidden: boolean },
+ embeddedInLabel?: { element: Element, hidden: boolean },
+ embeddedInNativeTextAlternative?: { element: Element, hidden: boolean },
+ embeddedInTargetElement?: 'self' | 'descendant',
};
function getTextAlternativeInternal(element: Element, options: AccessibleNameOptions): string {
@@ -525,7 +517,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
...options,
embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) },
embeddedInDescribedBy: undefined,
- embeddedInTargetElement: 'none',
+ embeddedInTargetElement: undefined,
embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined,
})).join(' ');
@@ -778,42 +770,7 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
!!options.embeddedInLabelledBy || !!options.embeddedInDescribedBy ||
!!options.embeddedInLabel || !!options.embeddedInNativeTextAlternative) {
options.visitedElements.add(element);
- const tokens: string[] = [];
- const visit = (node: Node, skipSlotted: boolean) => {
- if (skipSlotted && (node as Element | Text).assignedSlot)
- return;
- if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
- const display = getElementComputedStyle(node as Element)?.display || 'inline';
- let token = getTextAlternativeInternal(node as Element, childOptions);
- // SPEC DIFFERENCE.
- // Spec says "append the result to the accumulated text", assuming "with space".
- // However, multiple tests insist that inline elements do not add a space.
- // Additionally, insists on a space anyway, see "name_file-label-inline-block-elements-manual.html"
- if (display !== 'inline' || node.nodeName === 'BR')
- token = ' ' + token + ' ';
- tokens.push(token);
- } else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
- // step 2g.
- tokens.push(node.textContent || '');
- }
- };
- tokens.push(getPseudoContent(element, '::before'));
- const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
- if (assignedNodes.length) {
- for (const child of assignedNodes)
- visit(child, false);
- } else {
- for (let child = element.firstChild; child; child = child.nextSibling)
- visit(child, true);
- if (element.shadowRoot) {
- for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
- visit(child, true);
- }
- for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
- visit(owned, true);
- }
- tokens.push(getPseudoContent(element, '::after'));
- const accessibleName = tokens.join('');
+ const accessibleName = innerAccumulatedElementText(element, childOptions);
// Spec says "Return the accumulated text if it is not the empty string". However, that is not really
// compatible with the real browser behavior and wpt tests, where an element with empty contents will fallback to the title.
// So we follow the spec everywhere except for the target element itself. This can probably be improved.
@@ -834,6 +791,50 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
return '';
}
+function innerAccumulatedElementText(element: Element, options: AccessibleNameOptions): string {
+ const tokens: string[] = [];
+ const visit = (node: Node, skipSlotted: boolean) => {
+ if (skipSlotted && (node as Element | Text).assignedSlot)
+ return;
+ if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
+ const display = getElementComputedStyle(node as Element)?.display || 'inline';
+ let token = getTextAlternativeInternal(node as Element, options);
+ // SPEC DIFFERENCE.
+ // Spec says "append the result to the accumulated text", assuming "with space".
+ // However, multiple tests insist that inline elements do not add a space.
+ // Additionally, insists on a space anyway, see "name_file-label-inline-block-elements-manual.html"
+ if (display !== 'inline' || node.nodeName === 'BR')
+ token = ' ' + token + ' ';
+ tokens.push(token);
+ } else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
+ // step 2g.
+ tokens.push(node.textContent || '');
+ }
+ };
+ tokens.push(getPseudoContent(element, '::before'));
+ const assignedNodes = element.nodeName === 'SLOT' ? (element as HTMLSlotElement).assignedNodes() : [];
+ if (assignedNodes.length) {
+ for (const child of assignedNodes)
+ visit(child, false);
+ } else {
+ for (let child = element.firstChild; child; child = child.nextSibling)
+ visit(child, true);
+ if (element.shadowRoot) {
+ for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
+ visit(child, true);
+ }
+ for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
+ visit(owned, true);
+ }
+ tokens.push(getPseudoContent(element, '::after'));
+ return tokens.join('');
+}
+
+export function accumulatedElementText(element: Element): string {
+ const visitedElements = new Set();
+ return asFlatString(innerAccumulatedElementText(element, { visitedElements })).trim();
+}
+
export const kAriaSelectedRoles = ['gridcell', 'option', 'row', 'tab', 'rowheader', 'columnheader', 'treeitem'];
export function getAriaSelected(element: Element): boolean {
// https://www.w3.org/TR/wai-aria-1.2/#aria-selected
@@ -958,7 +959,7 @@ function getAccessibleNameFromAssociatedLabels(labels: Iterable !!accessibleName).join(' ');
}
diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts
index a694839f81..fd78f0c8d9 100644
--- a/packages/playwright/src/common/config.ts
+++ b/packages/playwright/src/common/config.ts
@@ -58,6 +58,9 @@ export class FullConfigInternal {
testIdMatcher?: Matcher;
defineConfigWasUsed = false;
+ globalSetups: string[] = [];
+ globalTeardowns: string[] = [];
+
constructor(location: ConfigLocation, userConfig: Config, configCLIOverrides: ConfigCLIOverrides) {
if (configCLIOverrides.projects && userConfig.projects)
throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
@@ -72,13 +75,16 @@ export class FullConfigInternal {
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig);
+ this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined);
+ this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined);
+
this.config = {
configFile: resolvedConfigFile,
rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
forbidOnly: takeFirst(configCLIOverrides.forbidOnly, userConfig.forbidOnly, false),
fullyParallel: takeFirst(configCLIOverrides.fullyParallel, userConfig.fullyParallel, false),
- globalSetup: takeFirst(resolveScript(userConfig.globalSetup, configDir), null),
- globalTeardown: takeFirst(resolveScript(userConfig.globalTeardown, configDir), null),
+ globalSetup: this.globalSetups[0] ?? null,
+ globalTeardown: this.globalTeardowns[0] ?? null,
globalTimeout: takeFirst(configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
grep: takeFirst(userConfig.grep, defaultGrep),
grepInvert: takeFirst(userConfig.grepInvert, null),
diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts
index eef56c4458..37a886d3e8 100644
--- a/packages/playwright/src/common/configLoader.ts
+++ b/packages/playwright/src/common/configLoader.ts
@@ -139,13 +139,25 @@ function validateConfig(file: string, config: Config) {
}
if ('globalSetup' in config && config.globalSetup !== undefined) {
- if (typeof config.globalSetup !== 'string')
+ if (Array.isArray(config.globalSetup)) {
+ config.globalSetup.forEach((item, index) => {
+ if (typeof item !== 'string')
+ throw errorWithFile(file, `config.globalSetup[${index}] must be a string`);
+ });
+ } else if (typeof config.globalSetup !== 'string') {
throw errorWithFile(file, `config.globalSetup must be a string`);
+ }
}
if ('globalTeardown' in config && config.globalTeardown !== undefined) {
- if (typeof config.globalTeardown !== 'string')
+ if (Array.isArray(config.globalTeardown)) {
+ config.globalTeardown.forEach((item, index) => {
+ if (typeof item !== 'string')
+ throw errorWithFile(file, `config.globalTeardown[${index}] must be a string`);
+ });
+ } else if (typeof config.globalTeardown !== 'string') {
throw errorWithFile(file, `config.globalTeardown must be a string`);
+ }
}
if ('globalTimeout' in config && config.globalTimeout !== undefined) {
diff --git a/packages/playwright/src/common/testType.ts b/packages/playwright/src/common/testType.ts
index adf6bc3734..f22fd159d8 100644
--- a/packages/playwright/src/common/testType.ts
+++ b/packages/playwright/src/common/testType.ts
@@ -52,6 +52,7 @@ export class TestTypeImpl {
test.skip = wrapFunctionWithLocation(this._modifier.bind(this, 'skip'));
test.fixme = wrapFunctionWithLocation(this._modifier.bind(this, 'fixme'));
test.fail = wrapFunctionWithLocation(this._modifier.bind(this, 'fail'));
+ test.fail.only = wrapFunctionWithLocation(this._createTest.bind(this, 'fail.only'));
test.slow = wrapFunctionWithLocation(this._modifier.bind(this, 'slow'));
test.setTimeout = wrapFunctionWithLocation(this._setTimeout.bind(this));
test.step = this._step.bind(this);
@@ -81,7 +82,7 @@ export class TestTypeImpl {
return suite;
}
- private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'fail', location: Location, title: string, fnOrDetails: Function | TestDetails, fn?: Function) {
+ private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'fail' | 'fail.only', location: Location, title: string, fnOrDetails: Function | TestDetails, fn?: Function) {
throwIfRunningInsideJest();
const suite = this._currentSuite(location, 'test()');
if (!suite)
@@ -104,10 +105,12 @@ export class TestTypeImpl {
test._tags.push(...validatedDetails.tags);
suite._addTest(test);
- if (type === 'only')
+ if (type === 'only' || type === 'fail.only')
test._only = true;
if (type === 'skip' || type === 'fixme' || type === 'fail')
test._staticAnnotations.push({ type });
+ else if (type === 'fail.only')
+ test._staticAnnotations.push({ type: 'fail' });
}
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, titleOrFn: string | Function, fnOrDetails?: TestDetails | Function, fn?: Function) {
diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts
index cd79ccab61..cf043c2ca8 100644
--- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts
+++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts
@@ -49,17 +49,19 @@ export async function toMatchAriaSnapshot(
const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
const notFound = received === kNoElementsFoundError;
+ const escapedExpected = escapePrivateUsePoints(expected);
+ const escapedReceived = escapePrivateUsePoints(received);
const message = () => {
if (pass) {
if (notFound)
- return messagePrefix + `Expected: not ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
- const printedReceived = printReceivedStringContainExpectedSubstring(received, received.indexOf(expected), expected.length);
- return messagePrefix + `Expected: not ${this.utils.printExpected(expected)}\nReceived string: ${printedReceived}` + callLogText(log);
+ return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log);
+ const printedReceived = printReceivedStringContainExpectedSubstring(escapedReceived, escapedReceived.indexOf(escapedExpected), escapedExpected.length);
+ return messagePrefix + `Expected: not ${this.utils.printExpected(escapedExpected)}\nReceived string: ${printedReceived}` + callLogText(log);
} else {
const labelExpected = `Expected`;
if (notFound)
- return messagePrefix + `${labelExpected}: ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
- return messagePrefix + this.utils.printDiffOrStringify(expected, received, labelExpected, 'Received string', false) + callLogText(log);
+ return messagePrefix + `${labelExpected}: ${this.utils.printExpected(escapedExpected)}\nReceived: ${escapedReceived}` + callLogText(log);
+ return messagePrefix + this.utils.printDiffOrStringify(escapedExpected, escapedReceived, labelExpected, 'Received string', false) + callLogText(log);
}
};
@@ -73,3 +75,7 @@ export async function toMatchAriaSnapshot(
timeout: timedOut ? timeout : undefined,
};
}
+
+function escapePrivateUsePoints(str: string) {
+ return str.replace(/[\uE000-\uF8FF]/g, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`);
+}
diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts
index 7403985bcb..d002381fb3 100644
--- a/packages/playwright/src/reporters/html.ts
+++ b/packages/playwright/src/reporters/html.ts
@@ -512,8 +512,8 @@ class HtmlBuilder {
error: step.error?.message,
count
};
- if (testStep.location)
- this._stepsInFile.set(testStep.location.file, testStep);
+ if (step.location)
+ this._stepsInFile.set(step.location.file, testStep);
return testStep;
}
diff --git a/packages/playwright/src/runner/tasks.ts b/packages/playwright/src/runner/tasks.ts
index 77d84419f4..528cac47cd 100644
--- a/packages/playwright/src/runner/tasks.ts
+++ b/packages/playwright/src/runner/tasks.ts
@@ -98,8 +98,11 @@ export function createGlobalSetupTasks(config: FullConfigInternal) {
if (!config.configCLIOverrides.preserveOutputDir && !process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS)
tasks.push(createRemoveOutputDirsTask());
tasks.push(...createPluginSetupTasks(config));
- if (config.config.globalSetup || config.config.globalTeardown)
- tasks.push(createGlobalSetupTask());
+ if (config.globalSetups.length || config.globalTeardowns.length) {
+ const length = Math.max(config.globalSetups.length, config.globalTeardowns.length);
+ for (let i = 0; i < length; i++)
+ tasks.push(createGlobalSetupTask(i, length));
+ }
return tasks;
}
@@ -161,15 +164,20 @@ function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task {
+function createGlobalSetupTask(index: number, length: number): Task {
let globalSetupResult: any;
let globalSetupFinished = false;
let teardownHook: any;
+
+ let title = 'global setup';
+ if (length > 1)
+ title += ` #${index}`;
+
return {
- title: 'global setup',
+ title,
setup: async ({ config }) => {
- const setupHook = config.config.globalSetup ? await loadGlobalHook(config, config.config.globalSetup) : undefined;
- teardownHook = config.config.globalTeardown ? await loadGlobalHook(config, config.config.globalTeardown) : undefined;
+ const setupHook = config.globalSetups[index] ? await loadGlobalHook(config, config.globalSetups[index]) : undefined;
+ teardownHook = config.globalTeardowns[index] ? await loadGlobalHook(config, config.globalTeardowns[index]) : undefined;
globalSetupResult = setupHook ? await setupHook(config.config) : undefined;
globalSetupFinished = true;
},
diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts
index c96de2091a..ae02d1506e 100644
--- a/packages/playwright/types/test.d.ts
+++ b/packages/playwright/types/test.d.ts
@@ -1077,7 +1077,8 @@ interface TestConfig {
/**
* Path to the global setup file. This file will be required and run before all the tests. It must export a single
- * function that takes a [FullConfig](https://playwright.dev/docs/api/class-fullconfig) argument.
+ * function that takes a [FullConfig](https://playwright.dev/docs/api/class-fullconfig) argument. Pass an array of
+ * paths to specify multiple global setup files.
*
* Learn more about [global setup and teardown](https://playwright.dev/docs/test-global-setup-teardown).
*
@@ -1093,12 +1094,13 @@ interface TestConfig {
* ```
*
*/
- globalSetup?: string;
+ globalSetup?: string|Array;
/**
* Path to the global teardown file. This file will be required and run after all the tests. It must export a single
* function. See also
- * [testConfig.globalSetup](https://playwright.dev/docs/api/class-testconfig#test-config-global-setup).
+ * [testConfig.globalSetup](https://playwright.dev/docs/api/class-testconfig#test-config-global-setup). Pass an array
+ * of paths to specify multiple global teardown files.
*
* Learn more about [global setup and teardown](https://playwright.dev/docs/test-global-setup-teardown).
*
@@ -1114,7 +1116,7 @@ interface TestConfig {
* ```
*
*/
- globalTeardown?: string;
+ globalTeardown?: string|Array;
/**
* Maximum time in milliseconds the whole test suite can run. Zero timeout (default) disables this behavior. Useful on
@@ -1857,8 +1859,316 @@ export type TestDetails = {
annotation?: TestDetailsAnnotation | TestDetailsAnnotation[];
}
-interface SuiteFunction {
+type TestBody = (args: TestArgs, testInfo: TestInfo) => Promise | void;
+type ConditionBody = (args: TestArgs) => boolean;
+
+/**
+ * Playwright Test provides a `test` function to declare tests and `expect` function to write assertions.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * const name = await page.innerText('.navbar__title');
+ * expect(name).toBe('Playwright');
+ * });
+ * ```
+ *
+ */
+export interface TestType {
/**
+ * Declares a test.
+ * - `test(title, body)`
+ * - `test(title, details, body)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ * ```
+ *
+ * **Tags**
+ *
+ * You can tag tests by providing additional test details. Alternatively, you can include tags in the test title. Note
+ * that each tag must start with `@` symbol.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', {
+ * tag: '@smoke',
+ * }, async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ *
+ * test('another test @smoke', async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ * ```
+ *
+ * Test tags are displayed in the test report, and are available to a custom reporter via `TestCase.tags` property.
+ *
+ * You can also filter tests by their tags during test execution:
+ * - in the [command line](https://playwright.dev/docs/test-cli#reference);
+ * - in the config with [testConfig.grep](https://playwright.dev/docs/api/class-testconfig#test-config-grep) and
+ * [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep);
+ *
+ * Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
+ *
+ * **Annotations**
+ *
+ * You can annotate tests by providing additional test details.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', {
+ * annotation: {
+ * type: 'issue',
+ * description: 'https://github.com/microsoft/playwright/issues/23180',
+ * },
+ * }, async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ * ```
+ *
+ * Test annotations are displayed in the test report, and are available to a custom reporter via
+ * `TestCase.annotations` property.
+ *
+ * You can also add annotations during runtime by manipulating
+ * [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
+ *
+ * Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
+ * @param title Test title.
+ * @param details Additional test details.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ */
+ (title: string, body: TestBody): void;
+ /**
+ * Declares a test.
+ * - `test(title, body)`
+ * - `test(title, details, body)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ * ```
+ *
+ * **Tags**
+ *
+ * You can tag tests by providing additional test details. Alternatively, you can include tags in the test title. Note
+ * that each tag must start with `@` symbol.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', {
+ * tag: '@smoke',
+ * }, async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ *
+ * test('another test @smoke', async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ * ```
+ *
+ * Test tags are displayed in the test report, and are available to a custom reporter via `TestCase.tags` property.
+ *
+ * You can also filter tests by their tags during test execution:
+ * - in the [command line](https://playwright.dev/docs/test-cli#reference);
+ * - in the config with [testConfig.grep](https://playwright.dev/docs/api/class-testconfig#test-config-grep) and
+ * [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep);
+ *
+ * Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
+ *
+ * **Annotations**
+ *
+ * You can annotate tests by providing additional test details.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('basic test', {
+ * annotation: {
+ * type: 'issue',
+ * description: 'https://github.com/microsoft/playwright/issues/23180',
+ * },
+ * }, async ({ page }) => {
+ * await page.goto('https://playwright.dev/');
+ * // ...
+ * });
+ * ```
+ *
+ * Test annotations are displayed in the test report, and are available to a custom reporter via
+ * `TestCase.annotations` property.
+ *
+ * You can also add annotations during runtime by manipulating
+ * [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
+ *
+ * Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
+ * @param title Test title.
+ * @param details Additional test details.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ */
+ (title: string, details: TestDetails, body: TestBody): void;
+
+ /**
+ * Declares a focused test. If there are some focused tests or suites, all of them will be run but nothing else.
+ * - `test.only(title, body)`
+ * - `test.only(title, details, body)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.only('focus this test', async ({ page }) => {
+ * // Run only focused tests in the entire project.
+ * });
+ * ```
+ *
+ * @param title Test title.
+ * @param details See [test.(call)(title[, details, body])](https://playwright.dev/docs/api/class-test#test-call) for test details
+ * description.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ */
+ only(title: string, body: TestBody): void;
+ /**
+ * Declares a focused test. If there are some focused tests or suites, all of them will be run but nothing else.
+ * - `test.only(title, body)`
+ * - `test.only(title, details, body)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.only('focus this test', async ({ page }) => {
+ * // Run only focused tests in the entire project.
+ * });
+ * ```
+ *
+ * @param title Test title.
+ * @param details See [test.(call)(title[, details, body])](https://playwright.dev/docs/api/class-test#test-call) for test details
+ * description.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ */
+ only(title: string, details: TestDetails, body: TestBody): void;
+
+ /**
+ * Declares a group of tests.
+ * - `test.describe(title, callback)`
+ * - `test.describe(callback)`
+ * - `test.describe(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * You can declare a group of tests with a title. The title will be visible in the test report as a part of each
+ * test's title.
+ *
+ * ```js
+ * test.describe('two tests', () => {
+ * test('one', async ({ page }) => {
+ * // ...
+ * });
+ *
+ * test('two', async ({ page }) => {
+ * // ...
+ * });
+ * });
+ * ```
+ *
+ * **Anonymous group**
+ *
+ * You can also declare a test group without a title. This is convenient to give a group of tests a common option with
+ * [test.use(options)](https://playwright.dev/docs/api/class-test#test-use).
+ *
+ * ```js
+ * test.describe(() => {
+ * test.use({ colorScheme: 'dark' });
+ *
+ * test('one', async ({ page }) => {
+ * // ...
+ * });
+ *
+ * test('two', async ({ page }) => {
+ * // ...
+ * });
+ * });
+ * ```
+ *
+ * **Tags**
+ *
+ * You can tag all tests in a group by providing additional details. Note that each tag must start with `@` symbol.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test.describe('two tagged tests', {
+ * tag: '@smoke',
+ * }, () => {
+ * test('one', async ({ page }) => {
+ * // ...
+ * });
+ *
+ * test('two', async ({ page }) => {
+ * // ...
+ * });
+ * });
+ * ```
+ *
+ * Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
+ *
+ * **Annotations**
+ *
+ * You can annotate all tests in a group by providing additional details.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test.describe('two annotated tests', {
+ * annotation: {
+ * type: 'issue',
+ * description: 'https://github.com/microsoft/playwright/issues/23180',
+ * },
+ * }, () => {
+ * test('one', async ({ page }) => {
+ * // ...
+ * });
+ *
+ * test('two', async ({ page }) => {
+ * // ...
+ * });
+ * });
+ * ```
+ *
+ * Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
+ * @param title Group title.
+ * @param details Additional details for all tests in the group.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Any tests
+ * declared in this callback will belong to the group.
+ */
+ describe: {
+ /**
* Declares a group of tests.
* - `test.describe(title, callback)`
* - `test.describe(callback)`
@@ -1953,7 +2263,7 @@ interface SuiteFunction {
* declared in this callback will belong to the group.
*/
(title: string, callback: () => void): void;
- /**
+ /**
* Declares a group of tests.
* - `test.describe(title, callback)`
* - `test.describe(callback)`
@@ -2048,7 +2358,7 @@ interface SuiteFunction {
* declared in this callback will belong to the group.
*/
(callback: () => void): void;
- /**
+ /**
* Declares a group of tests.
* - `test.describe(title, callback)`
* - `test.describe(callback)`
@@ -2143,295 +2453,7 @@ interface SuiteFunction {
* declared in this callback will belong to the group.
*/
(title: string, details: TestDetails, callback: () => void): void;
-}
-interface TestFunction {
- /**
- * Declares a test.
- * - `test(title, body)`
- * - `test(title, details, body)`
- *
- * **Usage**
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- * ```
- *
- * **Tags**
- *
- * You can tag tests by providing additional test details. Alternatively, you can include tags in the test title. Note
- * that each tag must start with `@` symbol.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', {
- * tag: '@smoke',
- * }, async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- *
- * test('another test @smoke', async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- * ```
- *
- * Test tags are displayed in the test report, and are available to a custom reporter via `TestCase.tags` property.
- *
- * You can also filter tests by their tags during test execution:
- * - in the [command line](https://playwright.dev/docs/test-cli#reference);
- * - in the config with [testConfig.grep](https://playwright.dev/docs/api/class-testconfig#test-config-grep) and
- * [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep);
- *
- * Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
- *
- * **Annotations**
- *
- * You can annotate tests by providing additional test details.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', {
- * annotation: {
- * type: 'issue',
- * description: 'https://github.com/microsoft/playwright/issues/23180',
- * },
- * }, async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- * ```
- *
- * Test annotations are displayed in the test report, and are available to a custom reporter via
- * `TestCase.annotations` property.
- *
- * You can also add annotations during runtime by manipulating
- * [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
- *
- * Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
- * @param title Test title.
- * @param details Additional test details.
- * @param body Test body that takes one or two arguments: an object with fixtures and optional
- * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
- */
- (title: string, body: (args: TestArgs, testInfo: TestInfo) => Promise | void): void;
- /**
- * Declares a test.
- * - `test(title, body)`
- * - `test(title, details, body)`
- *
- * **Usage**
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- * ```
- *
- * **Tags**
- *
- * You can tag tests by providing additional test details. Alternatively, you can include tags in the test title. Note
- * that each tag must start with `@` symbol.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', {
- * tag: '@smoke',
- * }, async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- *
- * test('another test @smoke', async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- * ```
- *
- * Test tags are displayed in the test report, and are available to a custom reporter via `TestCase.tags` property.
- *
- * You can also filter tests by their tags during test execution:
- * - in the [command line](https://playwright.dev/docs/test-cli#reference);
- * - in the config with [testConfig.grep](https://playwright.dev/docs/api/class-testconfig#test-config-grep) and
- * [testProject.grep](https://playwright.dev/docs/api/class-testproject#test-project-grep);
- *
- * Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
- *
- * **Annotations**
- *
- * You can annotate tests by providing additional test details.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', {
- * annotation: {
- * type: 'issue',
- * description: 'https://github.com/microsoft/playwright/issues/23180',
- * },
- * }, async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * // ...
- * });
- * ```
- *
- * Test annotations are displayed in the test report, and are available to a custom reporter via
- * `TestCase.annotations` property.
- *
- * You can also add annotations during runtime by manipulating
- * [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
- *
- * Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
- * @param title Test title.
- * @param details Additional test details.
- * @param body Test body that takes one or two arguments: an object with fixtures and optional
- * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
- */
- (title: string, details: TestDetails, body: (args: TestArgs, testInfo: TestInfo) => Promise | void): void;
-}
-
-/**
- * Playwright Test provides a `test` function to declare tests and `expect` function to write assertions.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test('basic test', async ({ page }) => {
- * await page.goto('https://playwright.dev/');
- * const name = await page.innerText('.navbar__title');
- * expect(name).toBe('Playwright');
- * });
- * ```
- *
- */
-export interface TestType extends TestFunction {
- /**
- * Declares a focused test. If there are some focused tests or suites, all of them will be run but nothing else.
- * - `test.only(title, body)`
- * - `test.only(title, details, body)`
- *
- * **Usage**
- *
- * ```js
- * test.only('focus this test', async ({ page }) => {
- * // Run only focused tests in the entire project.
- * });
- * ```
- *
- * @param title Test title.
- * @param details See [test.(call)(title[, details, body])](https://playwright.dev/docs/api/class-test#test-call) for test details
- * description.
- * @param body Test body that takes one or two arguments: an object with fixtures and optional
- * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
- */
- only: TestFunction;
- /**
- * Declares a group of tests.
- * - `test.describe(title, callback)`
- * - `test.describe(callback)`
- * - `test.describe(title, details, callback)`
- *
- * **Usage**
- *
- * You can declare a group of tests with a title. The title will be visible in the test report as a part of each
- * test's title.
- *
- * ```js
- * test.describe('two tests', () => {
- * test('one', async ({ page }) => {
- * // ...
- * });
- *
- * test('two', async ({ page }) => {
- * // ...
- * });
- * });
- * ```
- *
- * **Anonymous group**
- *
- * You can also declare a test group without a title. This is convenient to give a group of tests a common option with
- * [test.use(options)](https://playwright.dev/docs/api/class-test#test-use).
- *
- * ```js
- * test.describe(() => {
- * test.use({ colorScheme: 'dark' });
- *
- * test('one', async ({ page }) => {
- * // ...
- * });
- *
- * test('two', async ({ page }) => {
- * // ...
- * });
- * });
- * ```
- *
- * **Tags**
- *
- * You can tag all tests in a group by providing additional details. Note that each tag must start with `@` symbol.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test.describe('two tagged tests', {
- * tag: '@smoke',
- * }, () => {
- * test('one', async ({ page }) => {
- * // ...
- * });
- *
- * test('two', async ({ page }) => {
- * // ...
- * });
- * });
- * ```
- *
- * Learn more about [tagging](https://playwright.dev/docs/test-annotations#tag-tests).
- *
- * **Annotations**
- *
- * You can annotate all tests in a group by providing additional details.
- *
- * ```js
- * import { test, expect } from '@playwright/test';
- *
- * test.describe('two annotated tests', {
- * annotation: {
- * type: 'issue',
- * description: 'https://github.com/microsoft/playwright/issues/23180',
- * },
- * }, () => {
- * test('one', async ({ page }) => {
- * // ...
- * });
- *
- * test('two', async ({ page }) => {
- * // ...
- * });
- * });
- * ```
- *
- * Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
- * @param title Group title.
- * @param details Additional details for all tests in the group.
- * @param callback A callback that is run immediately when calling
- * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Any tests
- * declared in this callback will belong to the group.
- */
- describe: SuiteFunction & {
/**
* Declares a focused group of tests. If there are some focused tests or suites, all of them will be run but nothing
* else.
@@ -2467,7 +2489,80 @@ export interface TestType void): void;
+ /**
+ * Declares a focused group of tests. If there are some focused tests or suites, all of them will be run but nothing
+ * else.
+ * - `test.describe.only(title, callback)`
+ * - `test.describe.only(callback)`
+ * - `test.describe.only(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.only('focused group', () => {
+ * test('in the focused group', async ({ page }) => {
+ * // This test will run
+ * });
+ * });
+ * test('not in the focused group', async ({ page }) => {
+ * // This test will not run
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.only(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.only([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-only).
+ * Any tests added in this callback will belong to the group.
+ */
+ only(callback: () => void): void;
+ /**
+ * Declares a focused group of tests. If there are some focused tests or suites, all of them will be run but nothing
+ * else.
+ * - `test.describe.only(title, callback)`
+ * - `test.describe.only(callback)`
+ * - `test.describe.only(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.only('focused group', () => {
+ * test('in the focused group', async ({ page }) => {
+ * // This test will run
+ * });
+ * });
+ * test('not in the focused group', async ({ page }) => {
+ * // This test will not run
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.only(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.only([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-only).
+ * Any tests added in this callback will belong to the group.
+ */
+ only(title: string, details: TestDetails, callback: () => void): void;
+
/**
* Declares a skipped test group, similarly to
* [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Tests in the
@@ -2501,7 +2596,76 @@ export interface TestType void): void;
+ /**
+ * Declares a skipped test group, similarly to
+ * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Tests in the
+ * skipped group are never run.
+ * - `test.describe.skip(title, callback)`
+ * - `test.describe.skip(title)`
+ * - `test.describe.skip(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.skip('skipped group', () => {
+ * test('example', async ({ page }) => {
+ * // This test will not run
+ * });
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.skip(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.skip(title[, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-skip).
+ * Any tests added in this callback will belong to the group, and will not be run.
+ */
+ skip(callback: () => void): void;
+ /**
+ * Declares a skipped test group, similarly to
+ * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Tests in the
+ * skipped group are never run.
+ * - `test.describe.skip(title, callback)`
+ * - `test.describe.skip(title)`
+ * - `test.describe.skip(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.skip('skipped group', () => {
+ * test('example', async ({ page }) => {
+ * // This test will not run
+ * });
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.skip(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.skip(title[, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-skip).
+ * Any tests added in this callback will belong to the group, and will not be run.
+ */
+ skip(title: string, details: TestDetails, callback: () => void): void;
+
/**
* Declares a test group similarly to
* [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Tests in
@@ -2535,7 +2699,76 @@ export interface TestType void): void;
+ /**
+ * Declares a test group similarly to
+ * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Tests in
+ * this group are marked as "fixme" and will not be executed.
+ * - `test.describe.fixme(title, callback)`
+ * - `test.describe.fixme(callback)`
+ * - `test.describe.fixme(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.fixme('broken tests that should be fixed', () => {
+ * test('example', async ({ page }) => {
+ * // This test will not run
+ * });
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.fixme(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.fixme([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-fixme).
+ * Any tests added in this callback will belong to the group, and will not be run.
+ */
+ fixme(callback: () => void): void;
+ /**
+ * Declares a test group similarly to
+ * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe). Tests in
+ * this group are marked as "fixme" and will not be executed.
+ * - `test.describe.fixme(title, callback)`
+ * - `test.describe.fixme(callback)`
+ * - `test.describe.fixme(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.fixme('broken tests that should be fixed', () => {
+ * test('example', async ({ page }) => {
+ * // This test will not run
+ * });
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.fixme(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.fixme([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-fixme).
+ * Any tests added in this callback will belong to the group, and will not be run.
+ */
+ fixme(title: string, details: TestDetails, callback: () => void): void;
+
/**
* **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
* the preferred way of configuring the execution mode.
@@ -2574,7 +2807,125 @@ export interface TestType {
+ * test('runs first', async ({ page }) => {});
+ * test('runs second', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.serial(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.serial([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-serial).
+ * Any tests added in this callback will belong to the group.
+ */
+ (title: string, callback: () => void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a group of tests that should always be run serially. If one of the tests fails, all subsequent tests are
+ * skipped. All tests in a group are retried together.
+ *
+ * **NOTE** Using serial is not recommended. It is usually better to make your tests isolated, so they can be run
+ * independently.
+ *
+ * - `test.describe.serial(title, callback)`
+ * - `test.describe.serial(title)`
+ * - `test.describe.serial(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.serial('group', () => {
+ * test('runs first', async ({ page }) => {});
+ * test('runs second', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.serial(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.serial([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-serial).
+ * Any tests added in this callback will belong to the group.
+ */
+ (callback: () => void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a group of tests that should always be run serially. If one of the tests fails, all subsequent tests are
+ * skipped. All tests in a group are retried together.
+ *
+ * **NOTE** Using serial is not recommended. It is usually better to make your tests isolated, so they can be run
+ * independently.
+ *
+ * - `test.describe.serial(title, callback)`
+ * - `test.describe.serial(title)`
+ * - `test.describe.serial(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.serial('group', () => {
+ * test('runs first', async ({ page }) => {});
+ * test('runs second', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.serial(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.serial([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-serial).
+ * Any tests added in this callback will belong to the group.
+ */
+ (title: string, details: TestDetails, callback: () => void): void;
+
/**
* **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
* the preferred way of configuring the execution mode.
@@ -2616,8 +2967,93 @@ export interface TestType void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a focused group of tests that should always be run serially. If one of the tests fails, all subsequent
+ * tests are skipped. All tests in a group are retried together. If there are some focused tests or suites, all of
+ * them will be run but nothing else.
+ *
+ * **NOTE** Using serial is not recommended. It is usually better to make your tests isolated, so they can be run
+ * independently.
+ *
+ * - `test.describe.serial.only(title, callback)`
+ * - `test.describe.serial.only(title)`
+ * - `test.describe.serial.only(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.serial.only('group', () => {
+ * test('runs first', async ({ page }) => {
+ * });
+ * test('runs second', async ({ page }) => {
+ * });
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.serial.only(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.serial.only(title[, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-serial-only).
+ * Any tests added in this callback will belong to the group.
+ */
+ only(callback: () => void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a focused group of tests that should always be run serially. If one of the tests fails, all subsequent
+ * tests are skipped. All tests in a group are retried together. If there are some focused tests or suites, all of
+ * them will be run but nothing else.
+ *
+ * **NOTE** Using serial is not recommended. It is usually better to make your tests isolated, so they can be run
+ * independently.
+ *
+ * - `test.describe.serial.only(title, callback)`
+ * - `test.describe.serial.only(title)`
+ * - `test.describe.serial.only(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.serial.only('group', () => {
+ * test('runs first', async ({ page }) => {
+ * });
+ * test('runs second', async ({ page }) => {
+ * });
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.serial.only(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.serial.only(title[, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-serial-only).
+ * Any tests added in this callback will belong to the group.
+ */
+ only(title: string, details: TestDetails, callback: () => void): void;
};
+
/**
* **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
* the preferred way of configuring the execution mode.
@@ -2657,7 +3093,128 @@ export interface TestType {
+ * test('runs in parallel 1', async ({ page }) => {});
+ * test('runs in parallel 2', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * Note that parallel tests are executed in separate processes and cannot share any state or global variables. Each of
+ * the parallel tests executes all relevant hooks.
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.parallel(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel).
+ * Any tests added in this callback will belong to the group.
+ */
+ (title: string, callback: () => void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a group of tests that could be run in parallel. By default, tests in a single test file run one after
+ * another, but using
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel)
+ * allows them to run in parallel.
+ * - `test.describe.parallel(title, callback)`
+ * - `test.describe.parallel(callback)`
+ * - `test.describe.parallel(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.parallel('group', () => {
+ * test('runs in parallel 1', async ({ page }) => {});
+ * test('runs in parallel 2', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * Note that parallel tests are executed in separate processes and cannot share any state or global variables. Each of
+ * the parallel tests executes all relevant hooks.
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.parallel(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel).
+ * Any tests added in this callback will belong to the group.
+ */
+ (callback: () => void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a group of tests that could be run in parallel. By default, tests in a single test file run one after
+ * another, but using
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel)
+ * allows them to run in parallel.
+ * - `test.describe.parallel(title, callback)`
+ * - `test.describe.parallel(callback)`
+ * - `test.describe.parallel(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.parallel('group', () => {
+ * test('runs in parallel 1', async ({ page }) => {});
+ * test('runs in parallel 2', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * Note that parallel tests are executed in separate processes and cannot share any state or global variables. Each of
+ * the parallel tests executes all relevant hooks.
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.parallel(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel).
+ * Any tests added in this callback will belong to the group.
+ */
+ (title: string, details: TestDetails, callback: () => void): void;
+
/**
* **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
* the preferred way of configuring the execution mode.
@@ -2693,8 +3250,81 @@ export interface TestType void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a focused group of tests that could be run in parallel. This is similar to
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel),
+ * but focuses the group. If there are some focused tests or suites, all of them will be run but nothing else.
+ * - `test.describe.parallel.only(title, callback)`
+ * - `test.describe.parallel.only(callback)`
+ * - `test.describe.parallel.only(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.parallel.only('group', () => {
+ * test('runs in parallel 1', async ({ page }) => {});
+ * test('runs in parallel 2', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.parallel.only(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.parallel.only([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel-only).
+ * Any tests added in this callback will belong to the group.
+ */
+ only(callback: () => void): void;
+ /**
+ * **NOTE** See [test.describe.configure([options])](https://playwright.dev/docs/api/class-test#test-describe-configure) for
+ * the preferred way of configuring the execution mode.
+ *
+ * Declares a focused group of tests that could be run in parallel. This is similar to
+ * [test.describe.parallel([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel),
+ * but focuses the group. If there are some focused tests or suites, all of them will be run but nothing else.
+ * - `test.describe.parallel.only(title, callback)`
+ * - `test.describe.parallel.only(callback)`
+ * - `test.describe.parallel.only(title, details, callback)`
+ *
+ * **Usage**
+ *
+ * ```js
+ * test.describe.parallel.only('group', () => {
+ * test('runs in parallel 1', async ({ page }) => {});
+ * test('runs in parallel 2', async ({ page }) => {});
+ * });
+ * ```
+ *
+ * You can also omit the title.
+ *
+ * ```js
+ * test.describe.parallel.only(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Group title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for
+ * details description.
+ * @param callback A callback that is run immediately when calling
+ * [test.describe.parallel.only([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe-parallel-only).
+ * Any tests added in this callback will belong to the group.
+ */
+ only(title: string, details: TestDetails, callback: () => void): void;
};
+
/**
* Configures the enclosing scope. Can be executed either on the top level or inside a describe. Configuration applies
* to the entire scope, regardless of whether it run before or after the test declaration.
@@ -2754,6 +3384,7 @@ export interface TestType void;
};
+
/**
* Skip a test. Playwright will not run the test past the `test.skip()` call.
*
@@ -2834,7 +3465,7 @@ export interface TestType Promise | void): void;
+ skip(title: string, body: TestBody): void;
/**
* Skip a test. Playwright will not run the test past the `test.skip()` call.
*
@@ -2915,7 +3546,7 @@ export interface TestType Promise | void): void;
+ skip(title: string, details: TestDetails, body: TestBody): void;
/**
* Skip a test. Playwright will not run the test past the `test.skip()` call.
*
@@ -3158,7 +3789,8 @@ export interface TestType boolean, description?: string): void;
+ skip(callback: ConditionBody, description?: string): void;
+
/**
* Mark a test as "fixme", with the intention to fix it. Playwright will not run the test past the `test.fixme()`
* call.
@@ -3236,7 +3868,7 @@ export interface TestType Promise | void): void;
+ fixme(title: string, body: TestBody): void;
/**
* Mark a test as "fixme", with the intention to fix it. Playwright will not run the test past the `test.fixme()`
* call.
@@ -3314,7 +3946,7 @@ export interface TestType Promise | void): void;
+ fixme(title: string, details: TestDetails, body: TestBody): void;
/**
* Mark a test as "fixme", with the intention to fix it. Playwright will not run the test past the `test.fixme()`
* call.
@@ -3548,7 +4180,8 @@ export interface TestType boolean, description?: string): void;
+ fixme(callback: ConditionBody, description?: string): void;
+
/**
* Marks a test as "should fail". Playwright runs this test and ensures that it is actually failing. This is useful
* for documentation purposes to acknowledge that some functionality is broken until it is fixed.
@@ -3625,8 +4258,8 @@ export interface TestType Promise | void): void;
- /**
+ fail: {
+ /**
* Marks a test as "should fail". Playwright runs this test and ensures that it is actually failing. This is useful
* for documentation purposes to acknowledge that some functionality is broken until it is fixed.
*
@@ -3702,8 +4335,8 @@ export interface TestType Promise | void): void;
- /**
+ (title: string, body: TestBody): void;
+ /**
* Marks a test as "should fail". Playwright runs this test and ensures that it is actually failing. This is useful
* for documentation purposes to acknowledge that some functionality is broken until it is fixed.
*
@@ -3779,8 +4412,8 @@ export interface TestType): void;
+ /**
* Marks a test as "should fail". Playwright runs this test and ensures that it is actually failing. This is useful
* for documentation purposes to acknowledge that some functionality is broken until it is fixed.
*
@@ -3856,8 +4489,8 @@ export interface TestType boolean, description?: string): void;
- /**
+ (condition: boolean, description?: string): void;
+ /**
* Marks a test as "should fail". Playwright runs this test and ensures that it is actually failing. This is useful
* for documentation purposes to acknowledge that some functionality is broken until it is fixed.
*
@@ -3933,7 +4566,147 @@ export interface TestType, description?: string): void;
+ /**
+ * Marks a test as "should fail". Playwright runs this test and ensures that it is actually failing. This is useful
+ * for documentation purposes to acknowledge that some functionality is broken until it is fixed.
+ *
+ * To declare a "failing" test:
+ * - `test.fail(title, body)`
+ * - `test.fail(title, details, body)`
+ *
+ * To annotate test as "failing" at runtime:
+ * - `test.fail(condition, description)`
+ * - `test.fail(callback, description)`
+ * - `test.fail()`
+ *
+ * **Usage**
+ *
+ * You can declare a test as failing, so that Playwright ensures it actually fails.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test.fail('not yet ready', async ({ page }) => {
+ * // ...
+ * });
+ * ```
+ *
+ * If your test fails in some configurations, but not all, you can mark the test as failing inside the test body based
+ * on some condition. We recommend passing a `description` argument in this case.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('fail in WebKit', async ({ page, browserName }) => {
+ * test.fail(browserName === 'webkit', 'This feature is not implemented for Mac yet');
+ * // ...
+ * });
+ * ```
+ *
+ * You can mark all tests in a file or
+ * [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) group as
+ * "should fail" based on some condition with a single `test.fail(callback, description)` call.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test.fail(({ browserName }) => browserName === 'webkit', 'not implemented yet');
+ *
+ * test('fail in WebKit 1', async ({ page }) => {
+ * // ...
+ * });
+ * test('fail in WebKit 2', async ({ page }) => {
+ * // ...
+ * });
+ * ```
+ *
+ * You can also call `test.fail()` without arguments inside the test body to always mark the test as failed. We
+ * recommend declaring a failing test with `test.fail(title, body)` instead.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test('less readable', async ({ page }) => {
+ * test.fail();
+ * // ...
+ * });
+ * ```
+ *
+ * @param title Test title.
+ * @param details See [test.(call)(title[, details, body])](https://playwright.dev/docs/api/class-test#test-call) for test details
+ * description.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ * @param condition Test is marked as "should fail" when the condition is `true`.
+ * @param callback A function that returns whether to mark as "should fail", based on test fixtures. Test or tests are marked as
+ * "should fail" when the return value is `true`.
+ * @param description Optional description that will be reflected in a test report.
+ */
+ (): void;
+
+ /**
+ * You can use `test.fail.only` to focus on a specific test that is expected to fail. This is particularly useful when
+ * debugging a failing test or working on a specific issue.
+ *
+ * To declare a focused "failing" test:
+ * - `test.fail.only(title, body)`
+ * - `test.fail.only(title, details, body)`
+ *
+ * **Usage**
+ *
+ * You can declare a focused failing test, so that Playwright runs only this test and ensures it actually fails.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test.fail.only('focused failing test', async ({ page }) => {
+ * // This test is expected to fail
+ * });
+ * test('not in the focused group', async ({ page }) => {
+ * // This test will not run
+ * });
+ * ```
+ *
+ * @param title Test title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for test
+ * details description.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ */
+ only(title: string, body: TestBody): void;
+ /**
+ * You can use `test.fail.only` to focus on a specific test that is expected to fail. This is particularly useful when
+ * debugging a failing test or working on a specific issue.
+ *
+ * To declare a focused "failing" test:
+ * - `test.fail.only(title, body)`
+ * - `test.fail.only(title, details, body)`
+ *
+ * **Usage**
+ *
+ * You can declare a focused failing test, so that Playwright runs only this test and ensures it actually fails.
+ *
+ * ```js
+ * import { test, expect } from '@playwright/test';
+ *
+ * test.fail.only('focused failing test', async ({ page }) => {
+ * // This test is expected to fail
+ * });
+ * test('not in the focused group', async ({ page }) => {
+ * // This test will not run
+ * });
+ * ```
+ *
+ * @param title Test title.
+ * @param details See [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe) for test
+ * details description.
+ * @param body Test body that takes one or two arguments: an object with fixtures and optional
+ * [TestInfo](https://playwright.dev/docs/api/class-testinfo).
+ */
+ only(title: string, details: TestDetails, body: TestBody): void;
+ }
+
/**
* Marks a test as "slow". Slow test will be given triple the default timeout.
*
@@ -4107,7 +4880,8 @@ export interface TestType boolean, description?: string): void;
+ slow(callback: ConditionBody, description?: string): void;
+
/**
* Changes the timeout for the test. Zero means no timeout. Learn more about [various timeouts](https://playwright.dev/docs/test-timeouts).
*
diff --git a/packages/trace-viewer/src/ui/uiModeTestListView.css b/packages/trace-viewer/src/ui/uiModeTestListView.css
index ae6fd624ee..335daecfb1 100644
--- a/packages/trace-viewer/src/ui/uiModeTestListView.css
+++ b/packages/trace-viewer/src/ui/uiModeTestListView.css
@@ -14,28 +14,28 @@
limitations under the License.
*/
-.ui-mode-list-item {
+.ui-mode-tree-item {
flex: auto;
}
-.ui-mode-list-item-title {
+.ui-mode-tree-item-title {
flex: auto;
text-overflow: ellipsis;
overflow: hidden;
}
-.ui-mode-list-item-time {
+.ui-mode-tree-item-time {
flex: none;
color: var(--vscode-editorCodeLens-foreground);
margin: 0 4px;
user-select: none;
}
-.tests-list-view .list-view-entry.selected .ui-mode-list-item-time,
-.tests-list-view .list-view-entry.highlighted .ui-mode-list-item-time {
+.tests-tree-view .tree-view-entry.selected .ui-mode-tree-item-time,
+.tests-tree-view .tree-view-entry.highlighted .ui-mode-tree-item-time {
display: none;
}
-.tests-list-view .list-view-entry:not(.highlighted):not(.selected) .toolbar-button:not(.toggled) {
+.tests-tree-view .tree-view-entry:not(.highlighted):not(.selected) .toolbar-button:not(.toggled) {
display: none;
}
diff --git a/packages/trace-viewer/src/ui/uiModeTestListView.tsx b/packages/trace-viewer/src/ui/uiModeTestListView.tsx
index ce1c0fef37..96fbaadbf7 100644
--- a/packages/trace-viewer/src/ui/uiModeTestListView.tsx
+++ b/packages/trace-viewer/src/ui/uiModeTestListView.tsx
@@ -159,12 +159,12 @@ export const TestListView: React.FC<{
rootItem={testTree.rootItem}
dataTestId='test-tree'
render={treeItem => {
- return
+
+ `);
+
+ await checkAndMatchSnapshot(page.locator('body'), `
+ - link "world hello hello bye"
+ `);
+});
+
+it('should work with slots', async ({ page }) => {
+ // Text "foo" is assigned to the slot, should not be used twice.
+ await page.setContent(`
+
+
+ `);
+ await checkAndMatchSnapshot(page.locator('body'), `
+ - button "foo"
+ `);
+
+ // Text "foo" is assigned to the slot, should be used instead of slot content.
+ await page.setContent(`
+