Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
139 changes: 132 additions & 7 deletions docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,21 @@ The opposite of [`method: LocatorAssertions.toBeVisible`].
### option: LocatorAssertions.NotToBeVisible.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.18

## async method: LocatorAssertions.NotToContainClass
* since: v1.52
* langs: python

The opposite of [`method: LocatorAssertions.toContainClass`].

### param: LocatorAssertions.NotToContainClass.expected
* since: v1.52
- `expected` <[string]|[RegExp]|[Array]<[string]>|[Array]<[RegExp]>|[Array]<[string]|[RegExp]>>

Expected class or RegExp or a list of those.

### option: LocatorAssertions.NotToContainClass.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.52

## async method: LocatorAssertions.NotToContainText
* since: v1.20
* langs: python
Expand Down Expand Up @@ -1018,6 +1033,122 @@ await Expect(
### option: LocatorAssertions.toBeVisible.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.18

## async method: LocatorAssertions.toContainClass
* since: v1.52
* langs:
- alias-java: containsClass

Ensures the [Locator] points to an element with given CSS classes. All classes from the asserted value, separated by spaces, must be present in the [Element.classList](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) in any order.

**Usage**

```html
<div class='middle selected row' id='component'></div>
```

```js
const locator = page.locator('#component');
await expect(locator).toContainClass('middle selected row');
await expect(locator).toContainClass('selected');
await expect(locator).toContainClass('row middle');
```

```java
assertThat(page.locator("#component")).containsClass("middle selected row");
assertThat(page.locator("#component")).containsClass("selected");
assertThat(page.locator("#component")).containsClass("row middle");
```

```python async
from playwright.async_api import expect

locator = page.locator("#component")
await expect(locator).to_contain_class("middle selected row")
await expect(locator).to_contain_class("selected")
await expect(locator).to_contain_class("row middle")
```

```python sync
from playwright.sync_api import expect

locator = page.locator("#component")
expect(locator).to_contain_class("middle selected row")
expect(locator).to_contain_class("selected")
expect(locator).to_contain_class("row middle")
```

```csharp
var locator = Page.Locator("#component");
await Expect(locator).ToContainClassAsync("middle selected row");
await Expect(locator).ToContainClassAsync("selected");
await Expect(locator).ToContainClassAsync("row middle");
```

When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected class values. Each element's class attribute is matched against the corresponding string or regular expression in the array:

```html
<div class='list'></div>
<div class='component'></div>
<div class='component selected active-element'></div>
<div class='component'></div>
</div>
```

```js
const locator = page.locator('list > .component');
await expect(locator).toContainClass(['component', 'component selected', 'component']);
```

```java
assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
```

```python async
from playwright.async_api import expect

locator = page.locator("list > .component")
await expect(locator).to_have_class(["component", "component selected", "component"])
```

```python sync
from playwright.sync_api import expect

locator = page.locator("list > .component")
expect(locator).to_have_class(["component", "component selected", "component"])
```

```csharp
var locator = Page.Locator("list > .component");
await Expect(locator).ToContainClassAsync(new string[]{"component", "component selected", "component"});
```

### param: LocatorAssertions.toContainClass.expected
* since: v1.52
* langs: js
- `expected` <[string]|[RegExp]|[Array]<[string]|[RegExp]>>

Expected class or RegExp or a list of those.

### param: LocatorAssertions.toContainClass.expected
* since: v1.52
* langs: python
- `expected` <[string]|[RegExp]|[Array]<[string]>|[Array]<[RegExp]>|[Array]<[string]|[RegExp]>>

Expected class or RegExp or a list of those.

### param: LocatorAssertions.toContainClass.expected
* since: v1.52
* langs: java, csharp
- `expected` <[string]|[RegExp]|[Array]<[string]>|[Array]<[RegExp]>>

Expected class or RegExp or a list of those.

### option: LocatorAssertions.toContainClass.timeout = %%-js-assertions-timeout-%%
* since: v1.52

### option: LocatorAssertions.toContainClass.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.52

## async method: LocatorAssertions.toContainText
* since: v1.20
* langs:
Expand Down Expand Up @@ -1431,7 +1562,7 @@ Attribute name.
* langs:
- alias-java: hasClass

Ensures the [Locator] points to an element with given CSS classes. When a string is provided, it must fully match the element's `class` attribute. To match individual classes or perform partial matches use [`option: LocatorAssertions.toHaveClass.partial`].
Ensures the [Locator] points to an element with given CSS classes. When a string is provided, it must fully match the element's `class` attribute. To match individual classes or perform partial matches use [`method: LocatorAssertions.toContainClass`].

**Usage**

Expand Down Expand Up @@ -1527,12 +1658,6 @@ Expected class or RegExp or a list of those.

Expected class or RegExp or a list of those.

### option: LocatorAssertions.toHaveClass.partial
* since: v1.52
- `partial` <[boolean]>

Whether to perform a partial match, defaults to `false`. In an exact match, which is the default, the `className` attribute must be exactly the same as the asserted value. In a partial match, all classes from the asserted value, separated by spaces, must be present in the [Element.classList](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) in any order. Partial match does not support a regular expression.

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

Expand Down
10 changes: 5 additions & 5 deletions packages/injected/src/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1452,12 +1452,12 @@ export class InjectedScript {
if (value === null)
return { received: null, matches: false };
received = value;
} else if (expression === 'to.have.class') {
} else if (['to.have.class', 'to.contain.class'].includes(expression)) {
if (!options.expectedText)
throw this.createStacklessError('Expected text is not provided for ' + expression);
return {
received: element.classList.toString(),
matches: new ExpectedTextMatcher(this.builtins, options.expectedText[0]).matchesClassList(this, element.classList, options.expressionArg.partial),
matches: new ExpectedTextMatcher(this.builtins, options.expectedText[0]).matchesClassList(this, element.classList, /* partial */ expression === 'to.contain.class'),
};
} else if (expression === 'to.have.css') {
received = this.window.getComputedStyle(element).getPropertyValue(options.expressionArg);
Expand Down Expand Up @@ -1506,13 +1506,13 @@ export class InjectedScript {
if (!options.expectedText)
throw this.createStacklessError('Expected text is not provided for ' + expression);

if (expression === 'to.have.class.array') {
if (['to.have.class.array', 'to.contain.class.array'].includes(expression)) {
const receivedClassLists = elements.map(e => e.classList);
const received = receivedClassLists.map(String);
if (receivedClassLists.length !== options.expectedText.length)
return { received, matches: false };
const matches = this._matchSequentially(options.expectedText, receivedClassLists, (matcher, r) =>
matcher.matchesClassList(this, r, options.expressionArg.partial)
matcher.matchesClassList(this, r, /* partial */ expression === 'to.contain.class.array')
);
return {
received: received,
Expand Down Expand Up @@ -1644,7 +1644,7 @@ class ExpectedTextMatcher {
matchesClassList(injectedScript: InjectedScript, classList: DOMTokenList, partial: boolean): boolean {
if (partial) {
if (this._regex)
throw injectedScript.createStacklessError('Partial matching does not support regular expressions. Please provide a string value.');
return !!this._regex.test(classList.toString());
return this._string!.split(/\s+/g).filter(Boolean).every(className => classList.contains(className));
}
return this.matches(classList.toString());
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright/src/matchers/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
toBeInViewport,
toBeOK,
toBeVisible,
toContainClass,
toContainText,
toHaveAccessibleDescription,
toHaveAccessibleErrorMessage,
Expand Down Expand Up @@ -257,6 +258,7 @@ const customAsyncMatchers = {
toBeOK,
toBeVisible,
toContainText,
toContainClass,
toHaveAccessibleDescription,
toHaveAccessibleName,
toHaveAccessibleErrorMessage,
Expand Down
26 changes: 22 additions & 4 deletions packages/playwright/src/matchers/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,18 +252,36 @@ export function toHaveClass(
this: ExpectMatcherState,
locator: LocatorEx,
expected: string | RegExp | (string | RegExp)[],
options?: { timeout?: number, partial: boolean },
options?: { timeout?: number },
) {
const partial = options?.partial;
if (Array.isArray(expected)) {
return toEqual.call(this, 'toHaveClass', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues(expected);
return await locator._expect('to.have.class.array', { expectedText, expressionArg: { partial }, isNot, timeout });
return await locator._expect('to.have.class.array', { expectedText, isNot, timeout });
}, expected, options);
} else {
return toMatchText.call(this, 'toHaveClass', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues([expected]);
return await locator._expect('to.have.class', { expectedText, expressionArg: { partial }, isNot, timeout });
return await locator._expect('to.have.class', { expectedText, isNot, timeout });
}, expected, options);
}
}

export function toContainClass(
this: ExpectMatcherState,
locator: LocatorEx,
expected: string | RegExp | (string | RegExp)[],
options?: { timeout?: number },
) {
if (Array.isArray(expected)) {
return toEqual.call(this, 'toContainClass', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues(expected);
return await locator._expect('to.contain.class.array', { expectedText, isNot, timeout });
}, expected, options);
} else {
return toMatchText.call(this, 'toContainClass', locator, 'Locator', async (isNot, timeout) => {
const expectedText = serializeExpectedTextValues([expected]);
return await locator._expect('to.contain.class', { expectedText, isNot, timeout });
}, expected, options);
}
}
Expand Down
56 changes: 46 additions & 10 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8200,6 +8200,51 @@ interface LocatorAssertions {
visible?: boolean;
}): Promise<void>;

/**
* Ensures the [Locator](https://playwright.dev/docs/api/class-locator) points to an element with given CSS classes.
* All classes from the asserted value, separated by spaces, must be present in the
* [Element.classList](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) in any order.
*
* **Usage**
*
* ```html
* <div class='middle selected row' id='component'></div>
* ```
*
* ```js
* const locator = page.locator('#component');
* await expect(locator).toContainClass('middle selected row');
* await expect(locator).toContainClass('selected');
* await expect(locator).toContainClass('row middle');
* ```
*
* When an array is passed, the method asserts that the list of elements located matches the corresponding list of
* expected class values. Each element's class attribute is matched against the corresponding string or regular
* expression in the array:
*
* ```html
* <div class='list'></div>
* <div class='component'></div>
* <div class='component selected active-element'></div>
* <div class='component'></div>
* </div>
* ```
*
* ```js
* const locator = page.locator('list > .component');
* await expect(locator).toContainClass(['component', 'component selected', 'component']);
* ```
*
* @param expected Expected class or RegExp or a list of those.
* @param options
*/
toContainClass(expected: string|RegExp|ReadonlyArray<string|RegExp>, options?: {
/**
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;

/**
* Ensures the [Locator](https://playwright.dev/docs/api/class-locator) points to an element that contains the given
* text. All nested elements will be considered when computing the text content of the element. You can use regular
Expand Down Expand Up @@ -8409,7 +8454,7 @@ interface LocatorAssertions {
* Ensures the [Locator](https://playwright.dev/docs/api/class-locator) points to an element with given CSS classes.
* When a string is provided, it must fully match the element's `class` attribute. To match individual classes or
* perform partial matches use
* [`partial`](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-class-option-partial).
* [expect(locator).toContainClass(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-contain-class).
*
* **Usage**
*
Expand Down Expand Up @@ -8437,15 +8482,6 @@ interface LocatorAssertions {
* @param options
*/
toHaveClass(expected: string|RegExp|ReadonlyArray<string|RegExp>, options?: {
/**
* Whether to perform a partial match, defaults to `false`. In an exact match, which is the default, the `className`
* attribute must be exactly the same as the asserted value. In a partial match, all classes from the asserted value,
* separated by spaces, must be present in the
* [Element.classList](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) in any order. Partial match
* does not support a regular expression.
*/
partial?: boolean;

/**
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
*/
Expand Down
Loading
Loading