Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
93dfdbf
chore(typo): if you intend (#36259)
Skn0tt Jun 10, 2025
92994a8
fix: restore proper class name escaping (#36258)
dgozman Jun 10, 2025
3cb987f
fix(html-reporter): race condition where form submission used stale f…
mxschmitt Jun 10, 2025
a8f9c4d
test: unflake a few tests on Android (#36262)
dgozman Jun 10, 2025
d86787d
chore: roll stable-test-runner to 1.53.0-beta-1749049851000 (#36201)
mxschmitt Jun 10, 2025
c396674
test: add cookie with SameSite attribute (#36255)
yury-s Jun 10, 2025
c3c842c
fix(network): Include subdomains of localhost when including cookies …
simenbrekken Jun 10, 2025
df0e0fb
chore: make sure dispatchers work with SdkObjects (#36158)
dgozman Jun 11, 2025
d101492
fix(tests): lookup localhost subdomains on Win and Mac (#36285)
yury-s Jun 11, 2025
5d0d573
chore: remove PW_TEST_DISABLE_TRACING and _playwrightInstance (#36282)
dgozman Jun 11, 2025
3186574
chore: remove PLAYWRIGHT_SKIP_NAVIGATION_CHECK (#36283)
dgozman Jun 11, 2025
0c5d3f2
test: send secure cookies to subdomain.localhost (#36268)
yury-s Jun 11, 2025
64dd5ca
feat(firefox-beta): roll to r1483 (#36289)
microsoft-playwright-automation[bot] Jun 12, 2025
3c248ed
chore: remove PLAYWRIGHT_INPUT_FILE_TEXTBOX (#36281)
dgozman Jun 12, 2025
33d87d9
chore: accept Progress instance for raw input (#36280)
dgozman Jun 12, 2025
19718c0
chore: fix android socket on('close') (#36293)
dgozman Jun 12, 2025
bf3101f
feat(chromium): add local-fonts API permission (#36186)
mxschmitt Jun 12, 2025
1655ae9
feat(chromium): roll to r1179 (#36301)
microsoft-playwright-automation[bot] Jun 12, 2025
468237d
feat(chromium-tip-of-tree): roll to r1340 (#36306)
microsoft-playwright-automation[bot] Jun 12, 2025
15d033f
feat(webkit): roll to r2184 (#36309)
microsoft-playwright-automation[bot] Jun 13, 2025
0bf6c3d
chore: validate launchOptions options (#36276)
mxschmitt Jun 13, 2025
357ebfe
chore: make input actions "strict" in terms of timeout/abort (#36302)
dgozman Jun 16, 2025
fa9d67e
test: should fill programmatically enabled textarea (#36319)
Skn0tt Jun 16, 2025
a02722a
test: roll stable-test-runner to 1.54.0-alpha-2025-06-16 (#36322)
microsoft-playwright-automation[bot] Jun 16, 2025
6caf344
fix(list): avoid overwriting stdio logs from tests when writing statu…
agg23 Jun 16, 2025
baded72
feat(html): parse and render links in HTML report title (#36326)
agg23 Jun 16, 2025
1072d14
test: use role based selectors in trace-viewer tests (#36295)
mxschmitt Jun 16, 2025
764deda
chore: hide locator(':root') in Steps for toHaveTitle/URL (#36213)
mxschmitt Jun 16, 2025
90c6921
test: add failing oopif cdp bug (#36329)
Skn0tt Jun 16, 2025
114c9c0
chore(html): revert baded72 and use existing linkifyText (#36328)
agg23 Jun 16, 2025
ada2372
feat(webkit): roll to r2185 (#36335)
microsoft-playwright-automation[bot] Jun 17, 2025
a439191
chore: move HTTP server behaviour from WSServer to PlaywrightServer (…
Skn0tt Jun 17, 2025
4334911
feat(chromium-tip-of-tree): roll to r1341 (#36338)
microsoft-playwright-automation[bot] Jun 17, 2025
7e87033
feat(firefox): roll to r1488 (#36340)
microsoft-playwright-automation[bot] Jun 18, 2025
a7ff65c
feat(firefox-beta): roll to r1484 (#36341)
microsoft-playwright-automation[bot] Jun 18, 2025
8fcf838
chore: move some playwright-wide options to be per browser (#36342)
dgozman Jun 18, 2025
2576ce2
Revert "chore: reduce scrolling during clicks (#36175)" (#36346)
dgozman Jun 18, 2025
2973b0b
test: prevent indexeddb race conditions (#36347)
Skn0tt Jun 18, 2025
f050c3f
fix(trace): include method into "Fetch" action title (#36350)
dgozman Jun 18, 2025
50cb8a1
chore: prevent launching more browsers in server mode (#36353)
Skn0tt Jun 19, 2025
66e9030
chore: lift up playwright prelaunch (#36330)
Skn0tt Jun 19, 2025
5f65f32
test: update expectation for secure cookie test on WK Win (#36361)
yury-s Jun 19, 2025
07d1824
docs: correct spelling of 'informational' in README badge link (#36367)
mxschmitt Jun 20, 2025
ab7b18e
fix(ct): fsWatcher update comparison (#36366)
mxschmitt Jun 20, 2025
d4c0d75
chore: make fetch progress "strict" (#36318)
dgozman Jun 20, 2025
55cb7c9
chore: make navigation actions' progress "strict" (#36321)
dgozman Jun 20, 2025
20b8784
chore: make screenshot progress "strict" (#36323)
dgozman Jun 20, 2025
777d1e5
chore: use different babel import in tsxTransform (#36370)
mxschmitt Jun 20, 2025
173b455
fix(html-reporter): show filtered stats when filtering for labels/ann…
mxschmitt Jun 20, 2025
1357f0a
chore: simplify bidi browsers handling (#36363)
dgozman Jun 20, 2025
84c69ed
chore: make launch, newContext and newPage progress "strict" (#36336)
dgozman Jun 20, 2025
c0da193
chore: make various progress instances "strict" (#36349)
dgozman Jun 20, 2025
d3970a2
chore: smaller codex fixes (#36374)
mxschmitt Jun 20, 2025
71088f6
chore: refactor browser creation from PlaywrightConnection into Playw…
Skn0tt Jun 20, 2025
73f840c
chore: use isNonRetriableError in more places (#36373)
dgozman Jun 20, 2025
556fea9
fix: adding trialing slash detection logic back in urlToWSEndpoint (#…
stkevintan Jun 20, 2025
0027bd9
chore: browserserver, design two (#36382)
Skn0tt Jun 20, 2025
d8c257f
feat(chromium): roll to r1180 (#36384)
microsoft-playwright-automation[bot] Jun 20, 2025
41bcfc9
feat(webkit): roll to r2186 (#36391)
microsoft-playwright-automation[bot] Jun 21, 2025
ed7e552
chore: don't close other browsers in reuse-browsers mode (#36383)
Skn0tt Jun 23, 2025
07c4958
docs(clock): add snippets for 'Test with predefined time' for ports (…
mxschmitt Jun 23, 2025
6c26c5f
feat(chromium-tip-of-tree): roll to r1342 (#36379)
microsoft-playwright-automation[bot] Jun 23, 2025
06a065d
test: skip `should handle timeout properly 2` on tracing bots (#36399)
dgozman Jun 23, 2025
184fb04
test: roll stable-test-runner to 1.54.0-alpha-2025-06-23 (#36401)
microsoft-playwright-automation[bot] Jun 23, 2025
6693417
chore: make progress strict by default (#36389)
dgozman Jun 23, 2025
5013e2c
test: chromium tracing test rebase (#36403)
mxschmitt Jun 23, 2025
a5b68a5
devops: remove redundant scripts (#36408)
mxschmitt Jun 23, 2025
68d7f66
chore: move Page.close() tests to tests/library (#36390)
yury-s Jun 23, 2025
b1a1e11
chore: delete utils/doclint/generateFullConfigDoc.js (#36413)
mxschmitt Jun 23, 2025
896cb85
chore: fix Cannot find module '@testIsomorphic/types' in recorder (#3…
mxschmitt Jun 23, 2025
07e981f
fix: get rid of url.parse in network code
mxschmitt Jun 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/tests_others.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,27 @@ jobs:
env:
PW_CLOCK: ${{ matrix.clock }}

test_legacy_progress_timeouts:
name: legacy progress timeouts
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/run-test
with:
node-version: 20
browsers-to-install: chromium
command: npm run test -- --project=chromium-*
bot-name: "legacy-progress-timeouts-linux"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
env:
PLAYWRIGHT_LEGACY_TIMEOUTS: 1

test_electron:
name: Electron - ${{ matrix.os }}
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# 🎭 Playwright

[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.15-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord)
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-138.0.7204.35-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-139.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.5-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord)

## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)

Playwright is a framework for Web Testing and Automation. It allows testing [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->138.0.7204.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->138.0.7204.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->139.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

Expand Down
1 change: 1 addition & 0 deletions docs/src/api/class-browsercontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ Here are some permissions that may be supported by some browsers:
* `'notifications'`
* `'payment-handler'`
* `'storage-access'`
* `'local-fonts'`

### option: BrowserContext.grantPermissions.origin
* since: v1.8
Expand Down
41 changes: 41 additions & 0 deletions docs/src/clock.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,47 @@ await page.clock.setFixedTime(new Date('2024-02-02T10:30:00'));
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');
```

```python async
await page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 0, 0))
await page.goto("http://localhost:3333")
await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")

await page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 30, 0))
# We know that the page has a timer that updates the time every second.
await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```

```python sync
page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 0, 0))
page.goto("http://localhost:3333")
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
page.clock.set_fixed_time(datetime.datetime(2024, 2, 2, 10, 30, 0))
# We know that the page has a timer that updates the time every second.
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
```

```java
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
page.clock().setFixedTime(format.parse("2024-02-02T10:00:00"));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
page.clock().setFixedTime(format.parse("2024-02-02T10:30:00"));
// We know that the page has a timer that updates the time every second.
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
```

```csharp
// Set the fixed time for the clock.
await Page.Clock.SetFixedTimeAsync(new DateTime(2024, 2, 2, 10, 0, 0));
await Page.GotoAsync("http://localhost:3333");
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
// Set the fixed time for the clock.
await Page.Clock.SetFixedTimeAsync(new DateTime(2024, 2, 2, 10, 30, 0));
// We know that the page has a timer that updates the time every second.
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
```

## Consistent time and timers

Sometimes your timers depend on `Date.now` and are confused when the `Date.now` value does not change over time.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/test-api/class-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -1425,7 +1425,7 @@ Timeout in milliseconds.

Skip a test. Playwright will not run the test past the `test.skip()` call.

Skipped tests are not supposed to be ever run. If you intent to fix the test, use [`method: Test.fixme`] instead.
Skipped tests are not supposed to be ever run. If you intend to fix the test, use [`method: Test.fixme`] instead.

To declare a skipped test:
* `test.skip(title, body)`
Expand Down
6 changes: 3 additions & 3 deletions docs/src/test-reporters-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ List report supports the following configuration options and environment variabl
| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_LIST_PRINT_STEPS` | `printSteps` | Whether to print each step on its own line. | `false`
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. If a number is specified, it will also be used as the terminal width. | `true` when terminal is in TTY mode, `false` otherwise.
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.


Expand Down Expand Up @@ -140,7 +140,7 @@ Line report supports the following configuration options and environment variabl

| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. If a number is specified, it will also be used as the terminal width. | `true` when terminal is in TTY mode, `false` otherwise.
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.


Expand Down Expand Up @@ -182,7 +182,7 @@ Dot report supports the following configuration options and environment variable

| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. If a number is specified, it will also be used as the terminal width. | `true` when terminal is in TTY mode, `false` otherwise.
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.

### HTML reporter
Expand Down
5 changes: 4 additions & 1 deletion packages/html-reporter/src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export class Filter {
annotations: FilterToken[] = [];

empty(): boolean {
return this.project.length + this.status.length + this.text.length === 0;
return (
this.project.length + this.status.length + this.text.length +
this.labels.length + this.annotations.length
) === 0;
}

static parse(expression: string): Filter {
Expand Down
10 changes: 7 additions & 3 deletions packages/html-reporter/src/headerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as icons from './icons';
import { Link, navigate, SearchParamsContext } from './links';
import { statusIcon } from './statusIcon';
import { filterWithToken } from './filter';
import { linkifyText } from '@web/renderUtils';

export const HeaderView: React.FC<{
title: string | undefined,
Expand All @@ -35,7 +36,7 @@ export const HeaderView: React.FC<{
<div style={{ flex: 'auto' }}></div>
{rightSuperHeader}
</div>
{title && <div className='header-title'>{title}</div>}
{title && <div className='header-title'>{linkifyText(title)}</div>}
</div>;
};

Expand All @@ -60,13 +61,16 @@ export const GlobalFilterView: React.FC<{
event => {
event.preventDefault();
const url = new URL(window.location.href);
url.hash = filterText ? '?' + new URLSearchParams({ q: filterText }) : '';
// If <form/> onSubmit happens immediately after <input/> onChange, the filterText state is not updated yet.
// Using FormData here is a workaround to get the latest value.
const q = new FormData(event.target as HTMLFormElement).get('q') as string;
url.hash = q ? '?' + new URLSearchParams({ q }) : '';
navigate(url);
}
}>
{icons.search()}
{/* Use navigationId to reset defaultValue */}
<input spellCheck={false} className='form-control subnav-search-input input-contrast width-full' value={filterText} onChange={e => {
<input name='q' spellCheck={false} className='form-control subnav-search-input input-contrast width-full' value={filterText} onChange={e => {
setFilterText(e.target.value);
}}></input>
</form>
Expand Down
4 changes: 2 additions & 2 deletions packages/injected/src/ariaSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { escapeRegExp, longestCommonSubstring, normalizeWhiteSpace } from '@isomorphic/stringUtils';

import { box, getElementComputedStyle, getGlobalOptions, isElementVisible } from './domUtils';
import { box, getElementComputedStyle, isElementVisible } from './domUtils';
import * as roleUtils from './roleUtils';
import { yamlEscapeKeyIfNeeded, yamlEscapeValueIfNeeded } from './yaml';

Expand Down Expand Up @@ -214,7 +214,7 @@ function toAriaNode(element: Element, options?: { forAI?: boolean, refPrefix?: s
result.selected = roleUtils.getAriaSelected(element);

if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
if (element.type !== 'checkbox' && element.type !== 'radio' && (element.type !== 'file' || getGlobalOptions().inputFileRoleTextbox))
if (element.type !== 'checkbox' && element.type !== 'radio' && element.type !== 'file')
result.children = [element.value];
}

Expand Down
1 change: 0 additions & 1 deletion packages/injected/src/domUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

type GlobalOptions = {
browserNameForWorkarounds?: string;
inputFileRoleTextbox?: boolean;
};
let globalOptions: GlobalOptions = {};
export function setGlobalOptions(options: GlobalOptions) {
Expand Down
40 changes: 18 additions & 22 deletions packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ export type ElementState = 'visible' | 'hidden' | 'enabled' | 'disabled' | 'edit
export type ElementStateWithoutStable = Exclude<ElementState, 'stable'>;
export type ElementStateQueryResult = { matches: boolean, received?: string | 'error:notconnected' };

export type HitTargetError = { hitTargetDescription: string, hasPositionStickyOrFixed: boolean };
export type HitTargetInterceptionResult = {
stop: () => 'done' | HitTargetError;
stop: () => 'done' | { hitTargetDescription: string };
};

interface WebKitLegacyDeviceOrientationEvent extends DeviceOrientationEvent {
Expand All @@ -73,7 +72,6 @@ export type InjectedScriptOptions = {
testIdAttributeName: string;
stableRafCount: number;
browserName: string;
inputFileRoleTextbox: boolean;
customEngines: { name: string, source: string }[];
};

Expand Down Expand Up @@ -236,7 +234,7 @@ export class InjectedScript {

this._stableRafCount = options.stableRafCount;
this._browserName = options.browserName;
setGlobalOptions({ browserNameForWorkarounds: options.browserName, inputFileRoleTextbox: options.inputFileRoleTextbox });
setGlobalOptions({ browserNameForWorkarounds: options.browserName });

this._setupGlobalListenersRemovalDetection();
this._setupHitTargetInterceptors();
Expand Down Expand Up @@ -924,7 +922,7 @@ export class InjectedScript {
input.dispatchEvent(new Event('change', { bubbles: true }));
}

expectHitTarget(hitPoint: { x: number, y: number }, targetElement: Element): 'done' | HitTargetError {
expectHitTarget(hitPoint: { x: number, y: number }, targetElement: Element) {
const roots: (Document | ShadowRoot)[] = [];

// Get all component roots leading to the target element.
Expand Down Expand Up @@ -977,21 +975,14 @@ export class InjectedScript {

// Check whether hit target is the target or its descendant.
const hitParents: Element[] = [];
const isHitParentPositionStickyOrFixed: boolean[] = [];
while (hitElement && hitElement !== targetElement) {
hitParents.push(hitElement);
isHitParentPositionStickyOrFixed.push(['sticky', 'fixed'].includes(this.window.getComputedStyle(hitElement).position));
hitElement = parentElementOrShadowHost(hitElement);
}
if (hitElement === targetElement)
return 'done';

// The description of the element that was hit instead of the target element.
const hitTargetDescription = this.previewNode(hitParents[0] || this.document.documentElement);
// Whether any ancestor of the hit target has position: static. In this case, it could be
// beneficial to scroll the target element into different positions to reveal it.
let hasPositionStickyOrFixed = isHitParentPositionStickyOrFixed.some(x => x);

// Root is the topmost element in the hitTarget's chain that is not in the
// element's chain. For example, it might be a dialog element that overlays
// the target.
Expand All @@ -1002,14 +993,13 @@ export class InjectedScript {
if (index !== -1) {
if (index > 1)
rootHitTargetDescription = this.previewNode(hitParents[index - 1]);
hasPositionStickyOrFixed = isHitParentPositionStickyOrFixed.slice(0, index).some(x => x);
break;
}
element = parentElementOrShadowHost(element);
}
if (rootHitTargetDescription)
return { hitTargetDescription: `${hitTargetDescription} from ${rootHitTargetDescription} subtree`, hasPositionStickyOrFixed };
return { hitTargetDescription, hasPositionStickyOrFixed };
return { hitTargetDescription: `${hitTargetDescription} from ${rootHitTargetDescription} subtree` };
return { hitTargetDescription };
}

// Life of a pointer action, for example click.
Expand Down Expand Up @@ -1042,7 +1032,7 @@ export class InjectedScript {
// 2k. (injected) Event interceptor is removed.
// 2l. All navigations triggered between 2g-2k are awaited to be either committed or canceled.
// 2m. If failed, wait for increasing amount of time before the next retry.
setupHitTargetInterceptor(node: Node, action: 'hover' | 'tap' | 'mouse' | 'drag', hitPoint: { x: number, y: number } | undefined, blockAllEvents: boolean): HitTargetInterceptionResult | 'error:notconnected' | string /* JSON.stringify(hitTargetDescription) */ {
setupHitTargetInterceptor(node: Node, action: 'hover' | 'tap' | 'mouse' | 'drag', hitPoint: { x: number, y: number } | undefined, blockAllEvents: boolean): HitTargetInterceptionResult | 'error:notconnected' | string /* hitTargetDescription */ {
const element = this.retarget(node, 'button-link');
if (!element || !element.isConnected)
return 'error:notconnected';
Expand All @@ -1052,7 +1042,7 @@ export class InjectedScript {
// intercepting the action.
const preliminaryResult = this.expectHitTarget(hitPoint, element);
if (preliminaryResult !== 'done')
return JSON.stringify(preliminaryResult);
return preliminaryResult.hitTargetDescription;
}

// When dropping, the "element that is being dragged" often stays under the cursor,
Expand All @@ -1067,7 +1057,7 @@ export class InjectedScript {
'tap': this._tapHitTargetInterceptorEvents,
'mouse': this._mouseHitTargetInterceptorEvents,
}[action];
let result: 'done' | HitTargetError | undefined;
let result: 'done' | { hitTargetDescription: string } | undefined;

const listener = (event: PointerEvent | MouseEvent | TouchEvent) => {
// Ignore events that we do not expect to intercept.
Expand Down Expand Up @@ -1349,6 +1339,16 @@ export class InjectedScript {
// expect(locator).not.toBeInViewport() passes when there is no element.
if (options.isNot && options.expression === 'to.be.in.viewport')
return { matches: false };
if (options.expression === 'to.have.title' && options?.expectedText?.[0]) {
const matcher = new ExpectedTextMatcher(options.expectedText[0]);
const received = this.document.title;
return { received, matches: matcher.matches(received) };
}
if (options.expression === 'to.have.url' && options?.expectedText?.[0]) {
const matcher = new ExpectedTextMatcher(options.expectedText[0]);
const received = this.document.location.href;
return { received, matches: matcher.matches(received) };
}
// When none of the above applies, expect does not match.
return { matches: options.isNot, missingReceived: true };
}
Expand Down Expand Up @@ -1498,10 +1498,6 @@ export class InjectedScript {
received = getElementAccessibleErrorMessage(element);
} else if (expression === 'to.have.role') {
received = getAriaRole(element) || '';
} else if (expression === 'to.have.title') {
received = this.document.title;
} else if (expression === 'to.have.url') {
received = this.document.location.href;
} else if (expression === 'to.have.value') {
element = this.retarget(element, 'follow-label')!;
if (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')
Expand Down
Loading
Loading