feat: expect(locator).toHaveAttribute to assert attribute presence (#16767)

This patch changes `expect(locator).toHaveAttribute()` so that the
`value` argument can be omitted. When done so, the method will
assert attribute existance.

Fixes #16517
This commit is contained in:
Andrey Lushnikov 2022-08-25 05:28:34 -07:00 коммит произвёл GitHub
Родитель 6087c9abfd
Коммит 622c73cc1e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 31 добавлений и 11 удалений

Просмотреть файл

@ -890,11 +890,16 @@ Whether to use `element.innerText` instead of `element.textContent` when retriev
* langs: * langs:
- alias-java: hasAttribute - alias-java: hasAttribute
Ensures the [Locator] points to an element with given attribute. Ensures the [Locator] points to an element with given attribute. If the method
is used without `'value'` argument, then the method will assert attribute existance.
```js ```js
const locator = page.locator('input'); const locator = page.locator('input');
// Assert attribute with given value.
await expect(locator).toHaveAttribute('type', 'text'); await expect(locator).toHaveAttribute('type', 'text');
// Assert attribute existance.
await expect(locator).toHaveAttribute('disabled');
await expect(locator).not.toHaveAttribute('open');
``` ```
```java ```java
@ -928,9 +933,9 @@ Attribute name.
### param: LocatorAssertions.toHaveAttribute.value ### param: LocatorAssertions.toHaveAttribute.value
* since: v1.18 * since: v1.18
- `value` <[string]|[RegExp]> - `value` ?<[string]|[RegExp]>
Expected attribute value. Optional expected attribute value. If missing, method will assert attribute presence.
### option: LocatorAssertions.toHaveAttribute.timeout = %%-js-assertions-timeout-%% ### option: LocatorAssertions.toHaveAttribute.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18

Просмотреть файл

@ -1024,7 +1024,9 @@ export class InjectedScript {
{ {
// Element state / boolean values. // Element state / boolean values.
let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined; let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined;
if (expression === 'to.be.checked') { if (expression === 'to.have.attribute') {
elementState = element.hasAttribute(options.expressionArg);
} else if (expression === 'to.be.checked') {
elementState = progress.injectedScript.elementState(element, 'checked'); elementState = progress.injectedScript.elementState(element, 'checked');
} else if (expression === 'to.be.unchecked') { } else if (expression === 'to.be.unchecked') {
elementState = progress.injectedScript.elementState(element, 'unchecked'); elementState = progress.injectedScript.elementState(element, 'unchecked');
@ -1082,7 +1084,7 @@ export class InjectedScript {
{ {
// Single text value. // Single text value.
let received: string | undefined; let received: string | undefined;
if (expression === 'to.have.attribute') { if (expression === 'to.have.attribute.value') {
received = element.getAttribute(options.expressionArg) || ''; received = element.getAttribute(options.expressionArg) || '';
} else if (expression === 'to.have.class') { } else if (expression === 'to.have.class') {
received = element.classList.toString(); received = element.classList.toString();

Просмотреть файл

@ -138,12 +138,17 @@ export function toHaveAttribute(
this: ReturnType<Expect['getState']>, this: ReturnType<Expect['getState']>,
locator: LocatorEx, locator: LocatorEx,
name: string, name: string,
expected: string | RegExp, expected: string | RegExp | undefined,
options?: { timeout?: number }, options?: { timeout?: number },
) { ) {
if (expected === undefined) {
return toBeTruthy.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
return await locator._expect(customStackTrace, 'to.have.attribute', { expressionArg: name, isNot, timeout });
}, options);
}
return toMatchText.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout, customStackTrace) => { return toMatchText.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
const expectedText = toExpectedTextValues([expected]); const expectedText = toExpectedTextValues([expected]);
return await locator._expect(customStackTrace, 'to.have.attribute', { expressionArg: name, expectedText, isNot, timeout }); return await locator._expect(customStackTrace, 'to.have.attribute.value', { expressionArg: name, expectedText, isNot, timeout });
}, expected, options); }, expected, options);
} }

11
packages/playwright-test/types/test.d.ts поставляемый
Просмотреть файл

@ -3423,18 +3423,23 @@ interface LocatorAssertions {
}): Promise<void>; }): Promise<void>;
/** /**
* Ensures the [Locator] points to an element with given attribute. * Ensures the [Locator] points to an element with given attribute. If the method is used without `'value'` argument, then
* the method will assert attribute existance.
* *
* ```js * ```js
* const locator = page.locator('input'); * const locator = page.locator('input');
* // Assert attribute with given value.
* await expect(locator).toHaveAttribute('type', 'text'); * await expect(locator).toHaveAttribute('type', 'text');
* // Assert attribute existance.
* await expect(locator).toHaveAttribute('disabled');
* await expect(locator).not.toHaveAttribute('open');
* ``` * ```
* *
* @param name Attribute name. * @param name Attribute name.
* @param value Expected attribute value. * @param value Optional expected attribute value. If missing, method will assert attribute presence.
* @param options * @param options
*/ */
toHaveAttribute(name: string, value: string|RegExp, options?: { toHaveAttribute(name: string, value?: string|RegExp, options?: {
/** /**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`. * Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/ */

Просмотреть файл

@ -228,8 +228,11 @@ test.describe('toHaveURL', () => {
test.describe('toHaveAttribute', () => { test.describe('toHaveAttribute', () => {
test('pass', async ({ page }) => { test('pass', async ({ page }) => {
await page.setContent('<div id=node>Text content</div>'); await page.setContent('<div checked id=node>Text content</div>');
const locator = page.locator('#node'); const locator = page.locator('#node');
await expect(locator).toHaveAttribute('id');
await expect(locator).toHaveAttribute('checked');
await expect(locator).not.toHaveAttribute('open');
await expect(locator).toHaveAttribute('id', 'node'); await expect(locator).toHaveAttribute('id', 'node');
}); });
}); });