Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/src/actionability.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Playwright includes auto-retrying assertions that remove flakiness by waiting un
| [`method: LocatorAssertions.toHaveAttribute`] | Element has a DOM attribute |
| [`method: LocatorAssertions.toHaveClass`] | Element has a class property |
| [`method: LocatorAssertions.toHaveCount`] | List has exact number of children |
| [`method: LocatorAssertions.toHaveCSS#1`] | Element has CSS property |
| [`method: LocatorAssertions.toHaveCSS`] | Element has CSS property |
| [`method: LocatorAssertions.toHaveId`] | Element has an ID |
| [`method: LocatorAssertions.toHaveJSProperty`] | Element has a JavaScript property |
| [`method: LocatorAssertions.toHaveText`] | Element matches text |
Expand Down
49 changes: 10 additions & 39 deletions docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ Expected count.
* since: v1.20
* langs: python

The opposite of [`method: LocatorAssertions.toHaveCSS#1`].
The opposite of [`method: LocatorAssertions.toHaveCSS`].

### param: LocatorAssertions.NotToHaveCSS.name
* since: v1.18
Expand Down Expand Up @@ -1216,7 +1216,7 @@ await expect(page.locator('ul')).toContainText(['Text 3']);

```java
// ✓ Contains the right items in the right order
assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});

// ✖ Wrong order
assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
Expand All @@ -1232,7 +1232,7 @@ assertThat(page.locator("ul")).containsText(new String[] {"Text 3"});
from playwright.async_api import expect

# ✓ Contains the right items in the right order
await expect(page.locator("ul > li")).to_contain_text(["Text 1", "Text 3", "Text 4"])
await expect(page.locator("ul > li")).to_contain_text(["Text 1", "Text 3"])

# ✖ Wrong order
await expect(page.locator("ul > li")).to_contain_text(["Text 3", "Text 2"])
Expand All @@ -1248,7 +1248,7 @@ await expect(page.locator("ul")).to_contain_text(["Text 3"])
from playwright.sync_api import expect

# ✓ Contains the right items in the right order
expect(page.locator("ul > li")).to_contain_text(["Text 1", "Text 3", "Text 4"])
expect(page.locator("ul > li")).to_contain_text(["Text 1", "Text 3"])

# ✖ Wrong order
expect(page.locator("ul > li")).to_contain_text(["Text 3", "Text 2"])
Expand All @@ -1262,7 +1262,7 @@ expect(page.locator("ul")).to_contain_text(["Text 3"])

```csharp
// ✓ Contains the right items in the right order
await Expect(Page.Locator("ul > li")).ToContainTextAsync(new string[] {"Text 1", "Text 3", "Text 4"});
await Expect(Page.Locator("ul > li")).ToContainTextAsync(new string[] {"Text 1", "Text 3"});

// ✖ Wrong order
await Expect(Page.Locator("ul > li")).ToContainTextAsync(new string[] {"Text 3", "Text 2"});
Expand Down Expand Up @@ -1694,7 +1694,7 @@ Expected count.
### option: LocatorAssertions.toHaveCount.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.18

## async method: LocatorAssertions.toHaveCSS#1
## async method: LocatorAssertions.toHaveCSS
* since: v1.20
* langs:
- alias-java: hasCSS
Expand Down Expand Up @@ -1731,53 +1731,24 @@ var locator = Page.GetByRole(AriaRole.Button);
await Expect(locator).ToHaveCSSAsync("display", "flex");
```

### param: LocatorAssertions.toHaveCSS#1.name
### param: LocatorAssertions.toHaveCSS.name
* since: v1.18
- `name` <[string]>

CSS property name.

### param: LocatorAssertions.toHaveCSS#1.value
### param: LocatorAssertions.toHaveCSS.value
* since: v1.18
- `value` <[string]|[RegExp]>

CSS property value.

### option: LocatorAssertions.toHaveCSS#1.timeout = %%-js-assertions-timeout-%%
### option: LocatorAssertions.toHaveCSS.timeout = %%-js-assertions-timeout-%%
* since: v1.18

### option: LocatorAssertions.toHaveCSS#1.timeout = %%-csharp-java-python-assertions-timeout-%%
### option: LocatorAssertions.toHaveCSS.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.18

## async method: LocatorAssertions.toHaveCSS#2
* since: v1.58
* langs: js

Ensures the [Locator] resolves to an element with the given computed CSS properties.

:::note
The `CSSProperties` object parameter for toHaveCSS requires `react` to be installed for type checking.
:::

**Usage**

```js
const locator = page.getByRole('button');
await expect(locator).toHaveCSS({
display: 'flex',
backgroundColor: 'rgb(255, 0, 0)'
});
```

### param: LocatorAssertions.toHaveCSS#2.styles
* since: v1.58
- `styles` <[CSSProperties]>

CSS properties object.

### option: LocatorAssertions.toHaveCSS#2.timeout = %%-js-assertions-timeout-%%
* since: v1.58

## async method: LocatorAssertions.toHaveId
* since: v1.20
* langs:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/release-notes-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -3154,7 +3154,7 @@ List of all new assertions:
- [`expect(locator).toHaveAttribute(name, value)`](./api/class-locatorassertions#locator-assertions-to-have-attribute)
- [`expect(locator).toHaveClass(expected)`](./api/class-locatorassertions#locator-assertions-to-have-class)
- [`expect(locator).toHaveCount(count)`](./api/class-locatorassertions#locator-assertions-to-have-count)
- [`expect(locator).toHaveCSS(name, value)`](./api/class-locatorassertions#locator-assertions-to-have-css-1)
- [`expect(locator).toHaveCSS(name, value)`](./api/class-locatorassertions#locator-assertions-to-have-css)
- [`expect(locator).toHaveId(id)`](./api/class-locatorassertions#locator-assertions-to-have-id)
- [`expect(locator).toHaveJSProperty(name, value)`](./api/class-locatorassertions#locator-assertions-to-have-js-property)
- [`expect(locator).toHaveText(expected, options)`](./api/class-locatorassertions#locator-assertions-to-have-text)
Expand Down
2 changes: 1 addition & 1 deletion docs/src/test-assertions-csharp-java-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ title: "Assertions"
| [`method: LocatorAssertions.toHaveAttribute`] | Element has a DOM attribute |
| [`method: LocatorAssertions.toHaveClass`] | Element has a class property |
| [`method: LocatorAssertions.toHaveCount`] | List has exact number of children |
| [`method: LocatorAssertions.toHaveCSS#1`] | Element has CSS property |
| [`method: LocatorAssertions.toHaveCSS`] | Element has CSS property |
| [`method: LocatorAssertions.toHaveId`] | Element has an ID |
| [`method: LocatorAssertions.toHaveJSProperty`] | Element has a JavaScript property |
| [`method: LocatorAssertions.toHaveRole`] | Element has a specific [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles) |
Expand Down
2 changes: 1 addition & 1 deletion docs/src/test-assertions-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Note that retrying assertions are async, so you must `await` them.
| [await expect(locator).toHaveAttribute()](./api/class-locatorassertions.md#locator-assertions-to-have-attribute) | Element has a DOM attribute |
| [await expect(locator).toHaveClass()](./api/class-locatorassertions.md#locator-assertions-to-have-class) | Element has specified CSS class property |
| [await expect(locator).toHaveCount()](./api/class-locatorassertions.md#locator-assertions-to-have-count) | List has exact number of children |
| [await expect(locator).toHaveCSS()](./api/class-locatorassertions.md#locator-assertions-to-have-css-1) | Element has CSS property |
| [await expect(locator).toHaveCSS()](./api/class-locatorassertions.md#locator-assertions-to-have-css) | Element has CSS property |
| [await expect(locator).toHaveId()](./api/class-locatorassertions.md#locator-assertions-to-have-id) | Element has an ID |
| [await expect(locator).toHaveJSProperty()](./api/class-locatorassertions.md#locator-assertions-to-have-js-property) | Element has a JavaScript property |
| [await expect(locator).toHaveRole()](./api/class-locatorassertions.md#locator-assertions-to-have-role) | Element has a specific [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles) |
Expand Down
45 changes: 45 additions & 0 deletions packages/html-reporter/src/testResultView.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,51 @@
border-radius: 12px;
color: var(--color-canvas-default);
padding: 2px 8px;
line-height: normal;
}

.step-title-container {
display: flex;
align-items: center;
flex: auto;
min-width: 0;
}

.step-title-container > * {
flex-shrink: 0;
}

.step-title-text {
flex-shrink: 1;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
min-width: 0;
}

.step-spacer {
flex: auto;
}

.step-attachment-link {
display: flex;
flex: none;
border-radius: 4px;
padding: 4px;
}

.step-attachment-link:hover {
background-color: var(--color-neutral-muted);
}

.step-attachment-link .octicon {
margin-right: 0;
}

.step-duration {
flex: none;
white-space: nowrap;
margin-left: 4px;
}

:root.light-mode {
Expand Down
19 changes: 11 additions & 8 deletions packages/html-reporter/src/testResultView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,20 +194,23 @@ const StepTreeItem: React.FC<{
depth: number,
}> = ({ test, step, result, depth }) => {
const searchParams = useSearchParams();
return <TreeItem title={<span aria-label={step.title}>
<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
return <TreeItem title={<div aria-label={step.title} className='step-title-container'>
{statusIcon(step.error || step.duration === -1 ? 'failed' : (step.skipped ? 'skipped' : 'passed'))}
<span className='step-title-text'>
<span>{step.title}</span>
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>}
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
</span>
<span className='step-spacer'></span>
{step.attachments.length > 0 && <a
style={{ float: 'right' }}
className='step-attachment-link'
title={`reveal attachment`}
href={formatUrl(testResultHref({ test, result, anchor: `attachment-${step.attachments[0]}` }, searchParams))}
onClick={evt => { evt.stopPropagation(); }}>
{icons.attachment()}
</a>}
{statusIcon(step.error || step.duration === -1 ? 'failed' : (step.skipped ? 'skipped' : 'passed'))}
<span>{step.title}</span>
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>}
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
</span>} loadChildren={step.steps.length || step.snippet ? () => {
<span className='step-duration'>{msToString(step.duration)}</span>
</div>} loadChildren={step.steps.length || step.snippet ? () => {
const snippet = step.snippet ? [<CodeSnippet testId='test-snippet' key='line' code={step.snippet} />] : [];
const steps = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} />);
return snippet.concat(steps);
Expand Down
10 changes: 8 additions & 2 deletions packages/html-reporter/src/treeItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@
*/

.tree-item {
text-overflow: ellipsis;
display: flex;
flex-direction: column;
overflow: hidden;
white-space: nowrap;
min-width: 0;
line-height: 38px;
}

.tree-item-title {
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
display: flex;
align-items: center;
}

.tree-item-body {
Expand Down
4 changes: 2 additions & 2 deletions packages/html-reporter/src/treeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ export const TreeItem: React.FunctionComponent<{
}> = ({ title, loadChildren, onClick, expandByDefault, depth, style, flash }) => {
const [expanded, setExpanded] = React.useState(expandByDefault || false);
return <div role='treeitem' className={clsx('tree-item', flash && 'yellow-flash')} style={style}>
<span className='tree-item-title' style={{ whiteSpace: 'nowrap', paddingLeft: depth * 22 + 4 }} onClick={() => { onClick?.(); setExpanded(!expanded); }} >
<div className='tree-item-title' style={{ paddingLeft: depth * 22 + 4 }} onClick={() => { onClick?.(); setExpanded(!expanded); }} >
{loadChildren && !!expanded && icons.downArrow()}
{loadChildren && !expanded && icons.rightArrow()}
{!loadChildren && <span style={{ visibility: 'hidden' }}>{icons.rightArrow()}</span>}
{title}
</span>
</div>
{expanded && loadChildren?.()}
</div>;
};
2 changes: 0 additions & 2 deletions packages/playwright-core/src/client/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export const Events = {
},

Page: {
AgentTurn: 'agentturn',
Close: 'close',
Crash: 'crash',
Console: 'console',
Expand All @@ -79,7 +78,6 @@ export const Events = {
Worker: 'worker',
},


PageAgent: {
Turn: 'turn',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/pageAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class PageAgent extends ChannelOwner<channels.PageAgentChannel> implement
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.PageAgentInitializer) {
super(parent, type, guid, initializer);
this._page = Page.from(initializer.page);
this._channel.on('turn', params => this.emit(Events.Page.AgentTurn, params));
this._channel.on('turn', params => this.emit(Events.PageAgent.Turn, params));
}

async expect(expectation: string, options: channels.PageAgentExpectOptions = {}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export class PageAgentDispatcher extends Dispatcher<SdkObject, channels.PageAgen
}

async dispose(params: channels.PageAgentDisposeParams, progress: Progress): Promise<void> {
progress.metadata.potentiallyClosesScope = true;
void this.stopPendingOperations(new Error('The agent is disposed'));
this._dispose();
}

private _eventSupport(): loopTypes.LoopEvents {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class ProgressController {
(error as any)[kAbortErrorSymbol] = true;
this._state = { error };
this._forceAbortPromise.reject(error);
this._controller.abort();
this._controller.abort(error);
}
await this._donePromise;
}
Expand Down
5 changes: 0 additions & 5 deletions packages/playwright-core/src/utils/isomorphic/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ export function toSnakeCase(name: string): string {
return name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/([A-Z])([A-Z][a-z])/g, '$1_$2').toLowerCase();
}

export function toKebabCase(name: string): string {
// E.g. backgroundColor => background-color.
return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Z])([A-Z][a-z])/g, '$1-$2').toLowerCase();
}

export function formatObject(value: any, indent = ' ', mode: 'multiline' | 'oneline' = 'multiline'): string {
if (typeof value === 'string')
return escapeWithQuotes(value, '\'');
Expand Down
Loading
Loading