@serenity-js/web 3.34.2 → 3.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [3.35.1](https://github.com/serenity-js/serenity-js/compare/v3.35.0...v3.35.1) (2025-09-28)
7
+
8
+ **Note:** Version bump only for package @serenity-js/web
9
+
10
+
11
+
12
+
13
+
14
+ # [3.35.0](https://github.com/serenity-js/serenity-js/compare/v3.34.2...v3.35.0) (2025-09-07)
15
+
16
+
17
+ ### Features
18
+
19
+ * **web:** support for identifying page elements by their ARIA role ([cf3672a](https://github.com/serenity-js/serenity-js/commit/cf3672a6fe3051eab9c195f2f342ebf55c19e2d6))
20
+
21
+
22
+
23
+
24
+
6
25
  ## [3.34.2](https://github.com/serenity-js/serenity-js/compare/v3.34.1...v3.34.2) (2025-09-07)
7
26
 
8
27
  **Note:** Version bump only for package @serenity-js/web
@@ -1,33 +1,37 @@
1
- import type { Answerable } from '@serenity-js/core';
1
+ import type { Answerable, WithAnswerableProperties } from '@serenity-js/core';
2
2
  import { Question } from '@serenity-js/core';
3
3
  import { ByCss } from './ByCss';
4
4
  import { ByCssContainingText } from './ByCssContainingText';
5
5
  import { ById } from './ById';
6
+ import type { ByRoleSelectorOptions, ByRoleSelectorValue } from './ByRole';
7
+ import { ByRole } from './ByRole';
6
8
  import { ByTagName } from './ByTagName';
7
9
  import { ByXPath } from './ByXPath';
8
10
  /**
9
11
  * `By` produces a [`Selector`](https://serenity-js.org/api/web/class/Selector/) used to locate a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) or [`PageElement`](https://serenity-js.org/api/web/class/PageElements/) on a web page.
10
12
  * Selectors can be defined using a static value or a [`Question`](https://serenity-js.org/api/core/class/Question/) to be resolved at runtime.
11
13
  *
12
- * ### Defining a selector using a static value
14
+ * ## Defining a selector using a string
15
+ *
16
+ * Every selector method on this class accepts a static `string` value to define a selector.
13
17
  *
14
18
  * ```typescript
15
19
  * import { PageElement, By } from '@serenity-js/web'
16
20
  *
17
21
  * class LoginForm {
18
22
  * static usernameField = () =>
19
- * PageElement.located(By.id('username')) // locate element by its id
20
- * .describedAs('username field')
23
+ * PageElement.located(By.role('textbox', { name: 'Username' }))
24
+ * .describedAs('username field'),
21
25
  *
22
26
  * static passwordField = () =>
23
- * PageElement.located(By.css('[data-test="password"]')) // locate element using a CSS selector
27
+ * PageElement.located(By.css('[data-test-id="password"]'))
24
28
  * .describedAs('password field')
25
29
  * }
26
30
  * ```
27
31
  *
28
- * ### Defining a selector using a Question
32
+ * ## Defining a selector using a Question
29
33
  *
30
- * Each method on this class accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector.
34
+ * Each method on this class also accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector.
31
35
  * This can be useful when the selector is not known at the time of writing the test, or when the selector
32
36
  * needs to be calculated based on the state of the system under test.
33
37
  *
@@ -45,7 +49,7 @@ import { ByXPath } from './ByXPath';
45
49
  *
46
50
  * ```
47
51
  *
48
- * ### Learn more
52
+ * ## Learn more
49
53
  * - [Page Element Query Language](https://serenity-js.org/handbook/web-testing/page-element-query-language)
50
54
  * - [`PageElement`](https://serenity-js.org/api/web/class/PageElement/)
51
55
  * - [`PageElement`](https://serenity-js.org/api/web/class/PageElements/)
@@ -81,6 +85,74 @@ export declare class By {
81
85
  * @param selector
82
86
  */
83
87
  static id(selector: Answerable<string>): Question<Promise<ById>>;
88
+ /**
89
+ * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
90
+ * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
91
+ *
92
+ * ### Example usage
93
+ *
94
+ * Given the following HTML structure:
95
+ *
96
+ * ```html
97
+ * <h3>Sign up</h3>
98
+ * <label>
99
+ * <input type="checkbox" /> Subscribe
100
+ * </label>
101
+ * <br/>
102
+ * <button>Submit</button>
103
+ * ```
104
+ *
105
+ * Each element can be located by its implicit accessibility role:
106
+ *
107
+ * ```ts
108
+ * const heading = PageElement.located(By.role('heading', { name: 'Sign up' })).describedAs('Sign up heading');
109
+ * const checkbox = PageElement.located(By.role('checkbox', { name: 'Subscribe' })).describedAs('Subscribe checkbox');
110
+ * const button = PageElement.located(By.role('button', { name: 'Submit' })).describedAs('Submit button');
111
+ * ```
112
+ *
113
+ * #### Playwright Test
114
+ *
115
+ * ```ts
116
+ * import { Ensure } from '@serenity-js/assertions'
117
+ * import { Click, PageElement, By, isVisible } from '@serenity-js/web'
118
+ *
119
+ * // ... page element definitions as above
120
+ *
121
+ * describe('ARIA role selector', () => {
122
+ * it('locates an element by its accessible name', async ({ actor }) => {
123
+ * await actor.attemptsTo(
124
+ * Ensure.that(heading, isVisible()),
125
+ * Click.on(checkbox),
126
+ * Click.on(button),
127
+ * )
128
+ * })
129
+ * })
130
+ * ```
131
+ *
132
+ * #### WebdriverIO
133
+ *
134
+ * ```ts
135
+ * import { actorCalled } from '@serenity-js/core'
136
+ * import { Ensure } from '@serenity-js/assertions'
137
+ * import { Click, PageElement, By, isVisible } from '@serenity-js/web'
138
+ *
139
+ * // ... page element definitions as above
140
+ *
141
+ * describe('ARIA role selector', () => {
142
+ * it('locates an element by its accessible name', async () => {
143
+ * await actorCalled('Nick').attemptsTo(
144
+ * Ensure.that(heading, isVisible()),
145
+ * Click.on(checkbox),
146
+ * Click.on(button),
147
+ * )
148
+ * })
149
+ * })
150
+ * ```
151
+ *
152
+ * @param role
153
+ * @param options
154
+ */
155
+ static role(role: ByRoleSelectorValue, options?: Answerable<WithAnswerableProperties<ByRoleSelectorOptions>>): Question<Promise<ByRole>>;
84
156
  /**
85
157
  * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using the name of its [HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
86
158
  *
@@ -1 +1 @@
1
- {"version":3,"file":"By.d.ts","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/By.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,qBAAa,EAAE;IAEX;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAOlE;;;;;;OAMG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAQxH;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAOtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAOhE;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAO1E;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;CAMzE"}
1
+ {"version":3,"file":"By.d.ts","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/By.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAK,QAAQ,EAAO,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,qBAAa,EAAE;IAEX;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAOlE;;;;;;OAMG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAQxH;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAOtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAOhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkEG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,GAAE,UAAU,CAAC,wBAAwB,CAAC,qBAAqB,CAAC,CAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAwB5I;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAO1E;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;CAMzE"}
@@ -6,31 +6,34 @@ const ByCss_1 = require("./ByCss");
6
6
  const ByCssContainingText_1 = require("./ByCssContainingText");
7
7
  const ByDeepCss_1 = require("./ByDeepCss");
8
8
  const ById_1 = require("./ById");
9
+ const ByRole_1 = require("./ByRole");
9
10
  const ByTagName_1 = require("./ByTagName");
10
11
  const ByXPath_1 = require("./ByXPath");
11
12
  /**
12
13
  * `By` produces a [`Selector`](https://serenity-js.org/api/web/class/Selector/) used to locate a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) or [`PageElement`](https://serenity-js.org/api/web/class/PageElements/) on a web page.
13
14
  * Selectors can be defined using a static value or a [`Question`](https://serenity-js.org/api/core/class/Question/) to be resolved at runtime.
14
15
  *
15
- * ### Defining a selector using a static value
16
+ * ## Defining a selector using a string
17
+ *
18
+ * Every selector method on this class accepts a static `string` value to define a selector.
16
19
  *
17
20
  * ```typescript
18
21
  * import { PageElement, By } from '@serenity-js/web'
19
22
  *
20
23
  * class LoginForm {
21
24
  * static usernameField = () =>
22
- * PageElement.located(By.id('username')) // locate element by its id
23
- * .describedAs('username field')
25
+ * PageElement.located(By.role('textbox', { name: 'Username' }))
26
+ * .describedAs('username field'),
24
27
  *
25
28
  * static passwordField = () =>
26
- * PageElement.located(By.css('[data-test="password"]')) // locate element using a CSS selector
29
+ * PageElement.located(By.css('[data-test-id="password"]'))
27
30
  * .describedAs('password field')
28
31
  * }
29
32
  * ```
30
33
  *
31
- * ### Defining a selector using a Question
34
+ * ## Defining a selector using a Question
32
35
  *
33
- * Each method on this class accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector.
36
+ * Each method on this class also accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector.
34
37
  * This can be useful when the selector is not known at the time of writing the test, or when the selector
35
38
  * needs to be calculated based on the state of the system under test.
36
39
  *
@@ -48,7 +51,7 @@ const ByXPath_1 = require("./ByXPath");
48
51
  *
49
52
  * ```
50
53
  *
51
- * ### Learn more
54
+ * ## Learn more
52
55
  * - [Page Element Query Language](https://serenity-js.org/handbook/web-testing/page-element-query-language)
53
56
  * - [`PageElement`](https://serenity-js.org/api/web/class/PageElement/)
54
57
  * - [`PageElement`](https://serenity-js.org/api/web/class/PageElements/)
@@ -105,6 +108,92 @@ class By {
105
108
  return new ById_1.ById(bySelector);
106
109
  });
107
110
  }
111
+ /**
112
+ * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
113
+ * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
114
+ *
115
+ * ### Example usage
116
+ *
117
+ * Given the following HTML structure:
118
+ *
119
+ * ```html
120
+ * <h3>Sign up</h3>
121
+ * <label>
122
+ * <input type="checkbox" /> Subscribe
123
+ * </label>
124
+ * <br/>
125
+ * <button>Submit</button>
126
+ * ```
127
+ *
128
+ * Each element can be located by its implicit accessibility role:
129
+ *
130
+ * ```ts
131
+ * const heading = PageElement.located(By.role('heading', { name: 'Sign up' })).describedAs('Sign up heading');
132
+ * const checkbox = PageElement.located(By.role('checkbox', { name: 'Subscribe' })).describedAs('Subscribe checkbox');
133
+ * const button = PageElement.located(By.role('button', { name: 'Submit' })).describedAs('Submit button');
134
+ * ```
135
+ *
136
+ * #### Playwright Test
137
+ *
138
+ * ```ts
139
+ * import { Ensure } from '@serenity-js/assertions'
140
+ * import { Click, PageElement, By, isVisible } from '@serenity-js/web'
141
+ *
142
+ * // ... page element definitions as above
143
+ *
144
+ * describe('ARIA role selector', () => {
145
+ * it('locates an element by its accessible name', async ({ actor }) => {
146
+ * await actor.attemptsTo(
147
+ * Ensure.that(heading, isVisible()),
148
+ * Click.on(checkbox),
149
+ * Click.on(button),
150
+ * )
151
+ * })
152
+ * })
153
+ * ```
154
+ *
155
+ * #### WebdriverIO
156
+ *
157
+ * ```ts
158
+ * import { actorCalled } from '@serenity-js/core'
159
+ * import { Ensure } from '@serenity-js/assertions'
160
+ * import { Click, PageElement, By, isVisible } from '@serenity-js/web'
161
+ *
162
+ * // ... page element definitions as above
163
+ *
164
+ * describe('ARIA role selector', () => {
165
+ * it('locates an element by its accessible name', async () => {
166
+ * await actorCalled('Nick').attemptsTo(
167
+ * Ensure.that(heading, isVisible()),
168
+ * Click.on(checkbox),
169
+ * Click.on(button),
170
+ * )
171
+ * })
172
+ * })
173
+ * ```
174
+ *
175
+ * @param role
176
+ * @param options
177
+ */
178
+ static role(role, options = {}) {
179
+ const descriptionOf = (selectorOptions) => {
180
+ if (core_1.Question.isAQuestion(selectorOptions)) {
181
+ return (0, core_1.the) `by role ${role} (options: ${options})`;
182
+ }
183
+ if (Object.keys(selectorOptions).length === 0) {
184
+ return `by role "${role}"`;
185
+ }
186
+ const description = [];
187
+ for (const [key, value] of Object.entries(selectorOptions)) {
188
+ description.push(key + (0, core_1.f) `: ${value}`);
189
+ }
190
+ return `by role "${role}" (${description.join(', ')})`;
191
+ };
192
+ return core_1.Question.about(descriptionOf(options), async (actor) => {
193
+ const optionsValue = await actor.answer(core_1.Question.fromObject(options));
194
+ return new ByRole_1.ByRole(role, optionsValue);
195
+ });
196
+ }
108
197
  /**
109
198
  * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using the name of its [HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
110
199
  *
@@ -1 +1 @@
1
- {"version":3,"file":"By.js","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/By.ts"],"names":[],"mappings":";;;AACA,4CAAgD;AAEhD,mCAAgC;AAChC,+DAA4D;AAC5D,2CAAwC;AACxC,iCAA8B;AAC9B,2CAAwC;AACxC,uCAAoC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAa,EAAE;IAEX;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,QAA4B;QACnC,OAAO,eAAQ,CAAC,KAAK,CAAC,IAAA,QAAC,EAAA,WAAW,QAAQ,GAAG,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACzD,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,OAAO,IAAI,aAAK,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAA4B,EAAE,IAAwB;QAC3E,OAAO,eAAQ,CAAC,KAAK,CAAC,IAAA,QAAC,EAAA,WAAW,QAAQ,qBAAsB,IAAK,EAAE,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACnF,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9C,OAAO,IAAI,yCAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,QAA4B;QACvC,OAAO,eAAQ,CAAC,KAAK,CAAC,IAAA,QAAC,EAAA,gBAAgB,QAAQ,GAAG,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YAC9D,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,OAAO,IAAI,qBAAS,CAAC,UAAU,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,QAA4B;QAClC,OAAO,eAAQ,CAAC,KAAK,CAAC,IAAA,QAAC,EAAA,UAAU,QAAQ,GAAG,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACxD,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,OAAO,IAAI,WAAI,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,QAA4B;QACvC,OAAO,eAAQ,CAAC,KAAK,CAAC,IAAA,QAAC,EAAA,gBAAgB,QAAQ,GAAG,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YAC9D,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,OAAO,IAAI,qBAAS,CAAC,UAAU,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,QAA4B;QACrC,OAAO,eAAQ,CAAC,KAAK,CAAC,IAAA,QAAC,EAAA,aAAa,QAAQ,GAAG,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YAC3D,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,OAAO,IAAI,iBAAO,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AA7ED,gBA6EC"}
1
+ {"version":3,"file":"By.js","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/By.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import { Selector } from './Selector';
2
+ /**
3
+ * @group Models
4
+ */
5
+ export type ByRoleSelectorValue = 'alert' | 'alertdialog' | 'application' | 'article' | 'banner' | 'blockquote' | 'button' | 'caption' | 'cell' | 'checkbox' | 'code' | 'columnheader' | 'combobox' | 'complementary' | 'contentinfo' | 'definition' | 'deletion' | 'dialog' | 'directory' | 'document' | 'emphasis' | 'feed' | 'figure' | 'form' | 'generic' | 'grid' | 'gridcell' | 'group' | 'heading' | 'img' | 'insertion' | 'link' | 'list' | 'listbox' | 'listitem' | 'log' | 'main' | 'marquee' | 'math' | 'meter' | 'menu' | 'menubar' | 'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'navigation' | 'none' | 'note' | 'option' | 'paragraph' | 'presentation' | 'progressbar' | 'radio' | 'radiogroup' | 'region' | 'row' | 'rowgroup' | 'rowheader' | 'scrollbar' | 'search' | 'searchbox' | 'separator' | 'slider' | 'spinbutton' | 'status' | 'strong' | 'subscript' | 'superscript' | 'switch' | 'tab' | 'table' | 'tablist' | 'tabpanel' | 'term' | 'textbox' | 'time' | 'timer' | 'toolbar' | 'tooltip' | 'tree' | 'treegrid' | 'treeitem';
6
+ /**
7
+ * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
8
+ * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
9
+ *
10
+ * **Pro tip:** Instantiate using [`By.role`](https://serenity-js.org/api/web/class/By/#role)
11
+ *
12
+ * @group Models
13
+ */
14
+ export declare class ByRole extends Selector {
15
+ readonly value: ByRoleSelectorValue;
16
+ readonly options: ByRoleSelectorOptions;
17
+ constructor(value: ByRoleSelectorValue, options: ByRoleSelectorOptions);
18
+ }
19
+ /**
20
+ * @group Models
21
+ */
22
+ export interface ByRoleSelectorOptions {
23
+ /**
24
+ * An attribute that is usually set by [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked) or
25
+ * native `<input type=checkbox>` controls.
26
+ *
27
+ * :::tip Playwright only
28
+ * This property is supported only by Playwright.
29
+ * :::
30
+ */
31
+ checked?: boolean;
32
+ /**
33
+ * An attribute that is usually set by `aria-disabled` or `disabled`.
34
+ *
35
+ * **NOTE** Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about
36
+ * [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
37
+ *
38
+ * :::tip Playwright only
39
+ * This property is supported only by Playwright.
40
+ * :::
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Whether [`name`](https://serenity-js.org/api/web/interface/ByRoleSelectorOptions/#name) is matched exactly:
45
+ * case-sensitive and whole-string.
46
+ *
47
+ * :::tip Playwright matching defaults
48
+ * Playwright defaults to `false` for backwards compatibility, but we recommend setting this property to `true`
49
+ * to avoid unintentional matches.
50
+ * :::
51
+ *
52
+ * :::tip Playwright and name RegExp
53
+ * Playwright supports using a `RegExp` to match the name.
54
+ * When using a `RegExp`, exact matching is not applicable and this option is ignored.
55
+ * Note that **exact match still trims whitespace**.
56
+ * :::
57
+ *
58
+ * :::tip WebdriverIO
59
+ * WebdriverIO always performs exact matching, so this option is ignored.
60
+ * :::
61
+ */
62
+ exact?: boolean;
63
+ /**
64
+ * An attribute that is usually set by `aria-expanded`.
65
+ *
66
+ * Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
67
+ *
68
+ * :::tip Playwright only
69
+ * This property is supported only by Playwright.
70
+ * :::
71
+ */
72
+ expanded?: boolean;
73
+ /**
74
+ * Option that controls whether hidden elements are matched. By default, only non-hidden elements, as
75
+ * [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
76
+ *
77
+ * Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
78
+ *
79
+ * :::tip Playwright only
80
+ * This property is supported only by Playwright.
81
+ * :::
82
+ */
83
+ includeHidden?: boolean;
84
+ /**
85
+ * A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values
86
+ * for `<h1>-<h6>` elements.
87
+ *
88
+ * Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
89
+ *
90
+ * :::tip Playwright only
91
+ * This property is supported only by Playwright.
92
+ * :::
93
+ */
94
+ level?: number;
95
+ /**
96
+ * Option to match the [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
97
+ *
98
+ * :::tip Playwright
99
+ * Only Playwright supports using a `RegExp` to match the name. When using a `string` instead, Playwright performs
100
+ * a **case-insensitive** match and searches for a **substring**. Use
101
+ * [`exact`](https://serenity-js.org/api/web/interface/ByRoleSelectorOptions/#exact) to control this behavior.
102
+ * :::
103
+ *
104
+ * :::tip WebdriverIO
105
+ * WebdriverIO does not support using `RegExp` and performs a **case-sensitive** and **exact** matching.
106
+ * :::
107
+ */
108
+ name?: string | RegExp;
109
+ /**
110
+ * An attribute that is usually set by [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
111
+ *
112
+ * :::tip Playwright only
113
+ * This property is supported only by Playwright.
114
+ * :::
115
+ */
116
+ pressed?: boolean;
117
+ /**
118
+ * An attribute that is usually set by [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
119
+ *
120
+ * :::tip Playwright only
121
+ * This property is supported only by Playwright.
122
+ * :::
123
+ */
124
+ selected?: boolean;
125
+ }
126
+ //# sourceMappingURL=ByRole.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ByRole.d.ts","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/ByRole.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC;;GAEG;AACH,MAAM,MAAM,mBAAmB,GACzB,OAAO,GACP,aAAa,GACb,aAAa,GACb,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,SAAS,GACT,MAAM,GACN,UAAU,GACV,MAAM,GACN,cAAc,GACd,UAAU,GACV,eAAe,GACf,aAAa,GACb,YAAY,GACZ,UAAU,GACV,QAAQ,GACR,WAAW,GACX,UAAU,GACV,UAAU,GACV,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,GACT,MAAM,GACN,UAAU,GACV,OAAO,GACP,SAAS,GACT,KAAK,GACL,WAAW,GACX,MAAM,GACN,MAAM,GACN,SAAS,GACT,UAAU,GACV,KAAK,GACL,MAAM,GACN,SAAS,GACT,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,GACT,UAAU,GACV,kBAAkB,GAClB,eAAe,GACf,YAAY,GACZ,MAAM,GACN,MAAM,GACN,QAAQ,GACR,WAAW,GACX,cAAc,GACd,aAAa,GACb,OAAO,GACP,YAAY,GACZ,QAAQ,GACR,KAAK,GACL,UAAU,GACV,WAAW,GACX,WAAW,GACX,QAAQ,GACR,WAAW,GACX,WAAW,GACX,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,aAAa,GACb,QAAQ,GACR,KAAK,GACL,OAAO,GACP,SAAS,GACT,UAAU,GACV,MAAM,GACN,SAAS,GACT,MAAM,GACN,OAAO,GACP,SAAS,GACT,SAAS,GACT,MAAM,GACN,UAAU,GACV,UAAU,CAAA;AAEhB;;;;;;;GAOG;AACH,qBAAa,MAAO,SAAQ,QAAQ;aACJ,KAAK,EAAE,mBAAmB;aAAkB,OAAO,EAAE,qBAAqB;gBAA1E,KAAK,EAAE,mBAAmB,EAAkB,OAAO,EAAE,qBAAqB;CAGzG;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAElC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEvB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ByRole = void 0;
4
+ const Selector_1 = require("./Selector");
5
+ /**
6
+ * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
7
+ * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
8
+ *
9
+ * **Pro tip:** Instantiate using [`By.role`](https://serenity-js.org/api/web/class/By/#role)
10
+ *
11
+ * @group Models
12
+ */
13
+ class ByRole extends Selector_1.Selector {
14
+ value;
15
+ options;
16
+ constructor(value, options) {
17
+ super();
18
+ this.value = value;
19
+ this.options = options;
20
+ }
21
+ }
22
+ exports.ByRole = ByRole;
23
+ //# sourceMappingURL=ByRole.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ByRole.js","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/ByRole.ts"],"names":[],"mappings":";;;AAAA,yCAAsC;AAyFtC;;;;;;;GAOG;AACH,MAAa,MAAO,SAAQ,mBAAQ;IACJ;IAA4C;IAAxE,YAA4B,KAA0B,EAAkB,OAA8B;QAClG,KAAK,EAAE,CAAC;QADgB,UAAK,GAAL,KAAK,CAAqB;QAAkB,YAAO,GAAP,OAAO,CAAuB;IAEtG,CAAC;CACJ;AAJD,wBAIC"}
@@ -3,6 +3,7 @@ export * from './ByCss';
3
3
  export * from './ByCssContainingText';
4
4
  export * from './ByDeepCss';
5
5
  export * from './ById';
6
+ export * from './ByRole';
6
7
  export * from './ByTagName';
7
8
  export * from './ByXPath';
8
9
  export * from './Selector';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/index.ts"],"names":[],"mappings":"AAAA,cAAc,MAAM,CAAC;AACrB,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/index.ts"],"names":[],"mappings":"AAAA,cAAc,MAAM,CAAC;AACrB,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
@@ -19,6 +19,7 @@ __exportStar(require("./ByCss"), exports);
19
19
  __exportStar(require("./ByCssContainingText"), exports);
20
20
  __exportStar(require("./ByDeepCss"), exports);
21
21
  __exportStar(require("./ById"), exports);
22
+ __exportStar(require("./ByRole"), exports);
22
23
  __exportStar(require("./ByTagName"), exports);
23
24
  __exportStar(require("./ByXPath"), exports);
24
25
  __exportStar(require("./Selector"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uCAAqB;AACrB,0CAAwB;AACxB,wDAAsC;AACtC,8CAA4B;AAC5B,yCAAuB;AACvB,8CAA4B;AAC5B,4CAA0B;AAC1B,6CAA2B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/screenplay/models/selectors/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uCAAqB;AACrB,0CAAwB;AACxB,wDAAsC;AACtC,8CAA4B;AAC5B,yCAAuB;AACvB,2CAAyB;AACzB,8CAA4B;AAC5B,4CAA0B;AAC1B,6CAA2B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serenity-js/web",
3
- "version": "3.34.2",
3
+ "version": "3.35.1",
4
4
  "description": "Serenity/JS Screenplay Pattern library offering a flexible, web driver-agnostic approach for interacting with web-based user interfaces and components, suitable for various testing contexts",
5
5
  "author": {
6
6
  "name": "Jan Molak",
@@ -53,8 +53,8 @@
53
53
  "node": "^18.12 || ^20 || ^22"
54
54
  },
55
55
  "dependencies": {
56
- "@serenity-js/assertions": "3.34.2",
57
- "@serenity-js/core": "3.34.2",
56
+ "@serenity-js/assertions": "3.35.1",
57
+ "@serenity-js/core": "3.35.1",
58
58
  "tiny-types": "1.24.1"
59
59
  },
60
60
  "devDependencies": {
@@ -67,5 +67,5 @@
67
67
  "ts-node": "10.9.2",
68
68
  "typescript": "5.9.2"
69
69
  },
70
- "gitHead": "c2639f9771198d278cb595938d23e152c232b52d"
70
+ "gitHead": "f8b08ebf12dbf37a3176d4a27c67afd466ed9b92"
71
71
  }
@@ -1,10 +1,12 @@
1
- import type { Answerable} from '@serenity-js/core';
2
- import { f, Question } from '@serenity-js/core';
1
+ import type { Answerable, WithAnswerableProperties } from '@serenity-js/core';
2
+ import { f, Question, the } from '@serenity-js/core';
3
3
 
4
4
  import { ByCss } from './ByCss';
5
5
  import { ByCssContainingText } from './ByCssContainingText';
6
6
  import { ByDeepCss } from './ByDeepCss';
7
7
  import { ById } from './ById';
8
+ import type { ByRoleSelectorOptions, ByRoleSelectorValue } from './ByRole';
9
+ import { ByRole } from './ByRole';
8
10
  import { ByTagName } from './ByTagName';
9
11
  import { ByXPath } from './ByXPath';
10
12
 
@@ -12,25 +14,27 @@ import { ByXPath } from './ByXPath';
12
14
  * `By` produces a [`Selector`](https://serenity-js.org/api/web/class/Selector/) used to locate a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) or [`PageElement`](https://serenity-js.org/api/web/class/PageElements/) on a web page.
13
15
  * Selectors can be defined using a static value or a [`Question`](https://serenity-js.org/api/core/class/Question/) to be resolved at runtime.
14
16
  *
15
- * ### Defining a selector using a static value
17
+ * ## Defining a selector using a string
18
+ *
19
+ * Every selector method on this class accepts a static `string` value to define a selector.
16
20
  *
17
21
  * ```typescript
18
22
  * import { PageElement, By } from '@serenity-js/web'
19
23
  *
20
24
  * class LoginForm {
21
25
  * static usernameField = () =>
22
- * PageElement.located(By.id('username')) // locate element by its id
23
- * .describedAs('username field')
26
+ * PageElement.located(By.role('textbox', { name: 'Username' }))
27
+ * .describedAs('username field'),
24
28
  *
25
29
  * static passwordField = () =>
26
- * PageElement.located(By.css('[data-test="password"]')) // locate element using a CSS selector
30
+ * PageElement.located(By.css('[data-test-id="password"]'))
27
31
  * .describedAs('password field')
28
32
  * }
29
33
  * ```
30
34
  *
31
- * ### Defining a selector using a Question
35
+ * ## Defining a selector using a Question
32
36
  *
33
- * Each method on this class accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector.
37
+ * Each method on this class also accepts an [`Answerable`](https://serenity-js.org/api/core/#Answerable) to allow for dynamic resolution of the selector.
34
38
  * This can be useful when the selector is not known at the time of writing the test, or when the selector
35
39
  * needs to be calculated based on the state of the system under test.
36
40
  *
@@ -48,7 +52,7 @@ import { ByXPath } from './ByXPath';
48
52
  *
49
53
  * ```
50
54
  *
51
- * ### Learn more
55
+ * ## Learn more
52
56
  * - [Page Element Query Language](https://serenity-js.org/handbook/web-testing/page-element-query-language)
53
57
  * - [`PageElement`](https://serenity-js.org/api/web/class/PageElement/)
54
58
  * - [`PageElement`](https://serenity-js.org/api/web/class/PageElements/)
@@ -64,7 +68,7 @@ export class By {
64
68
  * @param selector
65
69
  */
66
70
  static css(selector: Answerable<string>): Question<Promise<ByCss>> {
67
- return Question.about(f`by css (${selector})`, async actor => {
71
+ return Question.about(f`by css (${ selector })`, async actor => {
68
72
  const bySelector = await actor.answer(selector);
69
73
  return new ByCss(bySelector);
70
74
  });
@@ -78,7 +82,7 @@ export class By {
78
82
  * @param text
79
83
  */
80
84
  static cssContainingText(selector: Answerable<string>, text: Answerable<string>): Question<Promise<ByCssContainingText>> {
81
- return Question.about(f`by css (${selector}) containing text ${ text }`, async actor => {
85
+ return Question.about(f`by css (${ selector }) containing text ${ text }`, async actor => {
82
86
  const bySelector = await actor.answer(selector);
83
87
  const textSelector = await actor.answer(text);
84
88
  return new ByCssContainingText(bySelector, textSelector);
@@ -92,7 +96,7 @@ export class By {
92
96
  * @param selector
93
97
  */
94
98
  static deepCss(selector: Answerable<string>): Question<Promise<ByCss>> {
95
- return Question.about(f`by deep css (${selector})`, async actor => {
99
+ return Question.about(f`by deep css (${ selector })`, async actor => {
96
100
  const bySelector = await actor.answer(selector);
97
101
  return new ByDeepCss(bySelector);
98
102
  });
@@ -104,19 +108,110 @@ export class By {
104
108
  * @param selector
105
109
  */
106
110
  static id(selector: Answerable<string>): Question<Promise<ById>> {
107
- return Question.about(f`by id (${selector})`, async actor => {
111
+ return Question.about(f`by id (${ selector })`, async actor => {
108
112
  const bySelector = await actor.answer(selector);
109
113
  return new ById(bySelector);
110
114
  });
111
115
  }
112
116
 
117
+ /**
118
+ * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
119
+ * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
120
+ *
121
+ * ### Example usage
122
+ *
123
+ * Given the following HTML structure:
124
+ *
125
+ * ```html
126
+ * <h3>Sign up</h3>
127
+ * <label>
128
+ * <input type="checkbox" /> Subscribe
129
+ * </label>
130
+ * <br/>
131
+ * <button>Submit</button>
132
+ * ```
133
+ *
134
+ * Each element can be located by its implicit accessibility role:
135
+ *
136
+ * ```ts
137
+ * const heading = PageElement.located(By.role('heading', { name: 'Sign up' })).describedAs('Sign up heading');
138
+ * const checkbox = PageElement.located(By.role('checkbox', { name: 'Subscribe' })).describedAs('Subscribe checkbox');
139
+ * const button = PageElement.located(By.role('button', { name: 'Submit' })).describedAs('Submit button');
140
+ * ```
141
+ *
142
+ * #### Playwright Test
143
+ *
144
+ * ```ts
145
+ * import { Ensure } from '@serenity-js/assertions'
146
+ * import { Click, PageElement, By, isVisible } from '@serenity-js/web'
147
+ *
148
+ * // ... page element definitions as above
149
+ *
150
+ * describe('ARIA role selector', () => {
151
+ * it('locates an element by its accessible name', async ({ actor }) => {
152
+ * await actor.attemptsTo(
153
+ * Ensure.that(heading, isVisible()),
154
+ * Click.on(checkbox),
155
+ * Click.on(button),
156
+ * )
157
+ * })
158
+ * })
159
+ * ```
160
+ *
161
+ * #### WebdriverIO
162
+ *
163
+ * ```ts
164
+ * import { actorCalled } from '@serenity-js/core'
165
+ * import { Ensure } from '@serenity-js/assertions'
166
+ * import { Click, PageElement, By, isVisible } from '@serenity-js/web'
167
+ *
168
+ * // ... page element definitions as above
169
+ *
170
+ * describe('ARIA role selector', () => {
171
+ * it('locates an element by its accessible name', async () => {
172
+ * await actorCalled('Nick').attemptsTo(
173
+ * Ensure.that(heading, isVisible()),
174
+ * Click.on(checkbox),
175
+ * Click.on(button),
176
+ * )
177
+ * })
178
+ * })
179
+ * ```
180
+ *
181
+ * @param role
182
+ * @param options
183
+ */
184
+ static role(role: ByRoleSelectorValue, options: Answerable<WithAnswerableProperties<ByRoleSelectorOptions>> = {}): Question<Promise<ByRole>> {
185
+ const descriptionOf = (selectorOptions: Answerable<WithAnswerableProperties<ByRoleSelectorOptions>>) => {
186
+ if (Question.isAQuestion(selectorOptions)) {
187
+ return the`by role ${ role } (options: ${ options })`
188
+ }
189
+
190
+ if (Object.keys(selectorOptions).length === 0) {
191
+ return `by role "${ role }"`;
192
+ }
193
+
194
+ const description = [];
195
+ for (const [ key, value ] of Object.entries(selectorOptions)) {
196
+ description.push(key + f`: ${ value }`);
197
+ }
198
+
199
+ return `by role "${ role }" (${ description.join(', ') })`;
200
+ }
201
+
202
+ return Question.about(descriptionOf(options), async actor => {
203
+ const optionsValue = await actor.answer(Question.fromObject(options)) as ByRoleSelectorOptions;
204
+ return new ByRole(role, optionsValue);
205
+ });
206
+ }
207
+
113
208
  /**
114
209
  * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) using the name of its [HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
115
210
  *
116
211
  * @param selector
117
212
  */
118
213
  static tagName(selector: Answerable<string>): Question<Promise<ByTagName>> {
119
- return Question.about(f`by tag name (${selector})`, async actor => {
214
+ return Question.about(f`by tag name (${ selector })`, async actor => {
120
215
  const bySelector = await actor.answer(selector);
121
216
  return new ByTagName(bySelector);
122
217
  });
@@ -128,7 +223,7 @@ export class By {
128
223
  * @param selector
129
224
  */
130
225
  static xpath(selector: Answerable<string>): Question<Promise<ByXPath>> {
131
- return Question.about(f`by xpath (${selector})`, async actor => {
226
+ return Question.about(f`by xpath (${ selector })`, async actor => {
132
227
  const bySelector = await actor.answer(selector);
133
228
  return new ByXPath(bySelector);
134
229
  });
@@ -0,0 +1,219 @@
1
+ import { Selector } from './Selector';
2
+
3
+ /**
4
+ * @group Models
5
+ */
6
+ export type ByRoleSelectorValue =
7
+ | 'alert'
8
+ | 'alertdialog'
9
+ | 'application'
10
+ | 'article'
11
+ | 'banner'
12
+ | 'blockquote'
13
+ | 'button'
14
+ | 'caption'
15
+ | 'cell'
16
+ | 'checkbox'
17
+ | 'code'
18
+ | 'columnheader'
19
+ | 'combobox'
20
+ | 'complementary'
21
+ | 'contentinfo'
22
+ | 'definition'
23
+ | 'deletion'
24
+ | 'dialog'
25
+ | 'directory'
26
+ | 'document'
27
+ | 'emphasis'
28
+ | 'feed'
29
+ | 'figure'
30
+ | 'form'
31
+ | 'generic'
32
+ | 'grid'
33
+ | 'gridcell'
34
+ | 'group'
35
+ | 'heading'
36
+ | 'img'
37
+ | 'insertion'
38
+ | 'link'
39
+ | 'list'
40
+ | 'listbox'
41
+ | 'listitem'
42
+ | 'log'
43
+ | 'main'
44
+ | 'marquee'
45
+ | 'math'
46
+ | 'meter'
47
+ | 'menu'
48
+ | 'menubar'
49
+ | 'menuitem'
50
+ | 'menuitemcheckbox'
51
+ | 'menuitemradio'
52
+ | 'navigation'
53
+ | 'none'
54
+ | 'note'
55
+ | 'option'
56
+ | 'paragraph'
57
+ | 'presentation'
58
+ | 'progressbar'
59
+ | 'radio'
60
+ | 'radiogroup'
61
+ | 'region'
62
+ | 'row'
63
+ | 'rowgroup'
64
+ | 'rowheader'
65
+ | 'scrollbar'
66
+ | 'search'
67
+ | 'searchbox'
68
+ | 'separator'
69
+ | 'slider'
70
+ | 'spinbutton'
71
+ | 'status'
72
+ | 'strong'
73
+ | 'subscript'
74
+ | 'superscript'
75
+ | 'switch'
76
+ | 'tab'
77
+ | 'table'
78
+ | 'tablist'
79
+ | 'tabpanel'
80
+ | 'term'
81
+ | 'textbox'
82
+ | 'time'
83
+ | 'timer'
84
+ | 'toolbar'
85
+ | 'tooltip'
86
+ | 'tree'
87
+ | 'treegrid'
88
+ | 'treeitem'
89
+
90
+ /**
91
+ * Locates a [`PageElement`](https://serenity-js.org/api/web/class/PageElement/) by its [ARIA role](https://www.w3.org/TR/wai-aria-1.2/#roles),
92
+ * [ARIA attributes](https://www.w3.org/TR/wai-aria-1.2/#aria-attributes) and [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
93
+ *
94
+ * **Pro tip:** Instantiate using [`By.role`](https://serenity-js.org/api/web/class/By/#role)
95
+ *
96
+ * @group Models
97
+ */
98
+ export class ByRole extends Selector {
99
+ constructor(public readonly value: ByRoleSelectorValue, public readonly options: ByRoleSelectorOptions) {
100
+ super();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * @group Models
106
+ */
107
+ export interface ByRoleSelectorOptions {
108
+
109
+ /**
110
+ * An attribute that is usually set by [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked) or
111
+ * native `<input type=checkbox>` controls.
112
+ *
113
+ * :::tip Playwright only
114
+ * This property is supported only by Playwright.
115
+ * :::
116
+ */
117
+ checked?: boolean;
118
+
119
+ /**
120
+ * An attribute that is usually set by `aria-disabled` or `disabled`.
121
+ *
122
+ * **NOTE** Unlike most other attributes, `disabled` is inherited through the DOM hierarchy. Learn more about
123
+ * [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled).
124
+ *
125
+ * :::tip Playwright only
126
+ * This property is supported only by Playwright.
127
+ * :::
128
+ */
129
+ disabled?: boolean;
130
+
131
+ /**
132
+ * Whether [`name`](https://serenity-js.org/api/web/interface/ByRoleSelectorOptions/#name) is matched exactly:
133
+ * case-sensitive and whole-string.
134
+ *
135
+ * :::tip Playwright matching defaults
136
+ * Playwright defaults to `false` for backwards compatibility, but we recommend setting this property to `true`
137
+ * to avoid unintentional matches.
138
+ * :::
139
+ *
140
+ * :::tip Playwright and name RegExp
141
+ * Playwright supports using a `RegExp` to match the name.
142
+ * When using a `RegExp`, exact matching is not applicable and this option is ignored.
143
+ * Note that **exact match still trims whitespace**.
144
+ * :::
145
+ *
146
+ * :::tip WebdriverIO
147
+ * WebdriverIO always performs exact matching, so this option is ignored.
148
+ * :::
149
+ */
150
+ exact?: boolean;
151
+
152
+ /**
153
+ * An attribute that is usually set by `aria-expanded`.
154
+ *
155
+ * Learn more about [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
156
+ *
157
+ * :::tip Playwright only
158
+ * This property is supported only by Playwright.
159
+ * :::
160
+ */
161
+ expanded?: boolean;
162
+
163
+ /**
164
+ * Option that controls whether hidden elements are matched. By default, only non-hidden elements, as
165
+ * [defined by ARIA](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), are matched by role selector.
166
+ *
167
+ * Learn more about [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden).
168
+ *
169
+ * :::tip Playwright only
170
+ * This property is supported only by Playwright.
171
+ * :::
172
+ */
173
+ includeHidden?: boolean;
174
+
175
+ /**
176
+ * A number attribute that is usually present for roles `heading`, `listitem`, `row`, `treeitem`, with default values
177
+ * for `<h1>-<h6>` elements.
178
+ *
179
+ * Learn more about [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level).
180
+ *
181
+ * :::tip Playwright only
182
+ * This property is supported only by Playwright.
183
+ * :::
184
+ */
185
+ level?: number;
186
+
187
+ /**
188
+ * Option to match the [accessible name](https://w3c.github.io/accname/#dfn-accessible-name).
189
+ *
190
+ * :::tip Playwright
191
+ * Only Playwright supports using a `RegExp` to match the name. When using a `string` instead, Playwright performs
192
+ * a **case-insensitive** match and searches for a **substring**. Use
193
+ * [`exact`](https://serenity-js.org/api/web/interface/ByRoleSelectorOptions/#exact) to control this behavior.
194
+ * :::
195
+ *
196
+ * :::tip WebdriverIO
197
+ * WebdriverIO does not support using `RegExp` and performs a **case-sensitive** and **exact** matching.
198
+ * :::
199
+ */
200
+ name?: string | RegExp;
201
+
202
+ /**
203
+ * An attribute that is usually set by [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed).
204
+ *
205
+ * :::tip Playwright only
206
+ * This property is supported only by Playwright.
207
+ * :::
208
+ */
209
+ pressed?: boolean;
210
+
211
+ /**
212
+ * An attribute that is usually set by [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected).
213
+ *
214
+ * :::tip Playwright only
215
+ * This property is supported only by Playwright.
216
+ * :::
217
+ */
218
+ selected?: boolean;
219
+ }
@@ -3,6 +3,7 @@ export * from './ByCss';
3
3
  export * from './ByCssContainingText';
4
4
  export * from './ByDeepCss';
5
5
  export * from './ById';
6
+ export * from './ByRole';
6
7
  export * from './ByTagName';
7
8
  export * from './ByXPath';
8
9
  export * from './Selector';