-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQueryPredicatePrototypeBuilder.cs
More file actions
498 lines (459 loc) · 33.8 KB
/
Copy pathQueryPredicatePrototypeBuilder.cs
File metadata and controls
498 lines (459 loc) · 33.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using CSF.Screenplay.Selenium.Actions;
using CSF.Screenplay.Selenium.Elements;
using CSF.Screenplay.Selenium.Queries;
using CSF.Specifications;
namespace CSF.Screenplay.Selenium.Builders
{
/// <summary>
/// Provides methods to build WebDriver predicate functions for a target element.
/// </summary>
/// <remarks>
/// <para>
/// These are generally used for WebDriver waits, to wait until a target element meets the specified conditions.
/// </para>
/// </remarks>
public class QueryPredicatePrototypeBuilder
{
readonly ITarget target;
readonly bool multiElement;
/// <summary>
/// Creates a query predicate based on a specified HTML attribute and a predicate for the attribute's value.
/// </summary>
/// <remarks>
/// <para>
/// Note that this specification makes use of Selenium's <c>GetAttribute</c> method.
/// The behaviour of that method in fact queries the DOM property of the element first, before querying the HTML attribute.
/// It also has special handling for certain attributes such as <c>class</c> & <c>readonly</c> attributes (substituting them with <c>className</c>
/// and <c>readOnly</c> respectively when querying DOM properties).
/// Finally, 'boolean' attribute values are returned with either the string <c>true</c> if present or <see langword="null"/> if not.
/// </para>
/// <para>
/// Note that the last behaviour, described above, means that this method may be used to match elements which <em>do not</em> have the specified attribute,
/// by using a predicate which checks for <see langword="null"/>.
/// </para>
/// </remarks>
/// <param name="attributeName">The name of the attribute to query.</param>
/// <param name="predicate">The predicate to apply to the attribute value.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> AttributeValue(string attributeName, Func<string,bool> predicate)
=> CreatePrototype(new AttributeQuery(attributeName), Spec.Func(predicate), t => $"{t.Name} has attribute '{attributeName}' matching a predicate");
/// <summary>
/// Creates a query predicate for the presence of a specified HTML attribute, with a specified value.
/// </summary>
/// <remarks>
/// <para>
/// Note that this specification makes use of Selenium's <c>GetAttribute</c> method.
/// The behaviour of that method in fact queries the DOM property of the element first, before querying the HTML attribute.
/// It also has special handling for certain attributes such as <c>class</c> & <c>readonly</c> attributes (substituting them with <c>className</c>
/// and <c>readOnly</c> respectively when querying DOM properties).
/// Finally, 'boolean' attribute values are returned with either the string <c>true</c> if present or <see langword="null"/> if not.
/// </para>
/// </remarks>
/// <param name="attributeName">The name of the attribute to query.</param>
/// <param name="value">The value of the attribute to query.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> AttributeValue(string attributeName, string value)
=> CreatePrototype(new AttributeQuery(attributeName), Spec.Func<string>(x => x == value), t => $"{t.Name} has attribute '{attributeName}' with value '{value}'");
/// <summary>
/// Creates a query predicate for the presence of a specified HTML attribute.
/// </summary>
/// <remarks>
/// <para>
/// Note that this specification makes use of Selenium's <c>GetAttribute</c> method.
/// The behaviour of that method in fact queries the DOM property of the element first, before querying the HTML attribute.
/// It also has special handling for certain attributes such as <c>class</c> & <c>readonly</c> attributes (substituting them with <c>className</c>
/// and <c>readOnly</c> respectively when querying DOM properties).
/// Finally, 'boolean' attribute values are returned with either the string <c>true</c> if present or <see langword="null"/> if not.
/// </para>
/// </remarks>
/// <param name="attributeName">The name of the attribute to query.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Attribute(string attributeName)
=> CreatePrototype(new AttributeQuery(attributeName), Spec.Func<string>(x => x != null), t => $"{t.Name} has the attribute '{attributeName}'");
/// <summary>
/// Creates a query predicate for the presence of a specified HTML class (amongst the class attribute's values).
/// </summary>
/// <remarks>
/// <para>
/// Note that this specification makes use of Selenium's <c>GetAttribute</c> method.
/// The behaviour of that method in fact queries the DOM property of the element first, before querying the HTML attribute.
/// It also has special handling for certain attributes such as <c>class</c> & <c>readonly</c> attributes (substituting them with <c>className</c>
/// and <c>readOnly</c> respectively when querying DOM properties).
/// Finally, 'boolean' attribute values are returned with either the string <c>true</c> if present or <see langword="null"/> if not.
/// </para>
/// </remarks>
/// <param name="class">The name of the class to query.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Class(string @class)
=> CreatePrototype(new AttributeQuery(AttributeQuery.ClassAttribute), Spec.Func<string>(x => HasClass(x, @class)), t => $"{t.Name} has the class '{@class}'");
/// <summary>
/// Creates a query predicate for the absence of a specified HTML class (amongst the class attribute's values).
/// </summary>
/// <remarks>
/// <para>
/// Note that this specification makes use of Selenium's <c>GetAttribute</c> method.
/// The behaviour of that method in fact queries the DOM property of the element first, before querying the HTML attribute.
/// It also has special handling for certain attributes such as <c>class</c> & <c>readonly</c> attributes (substituting them with <c>className</c>
/// and <c>readOnly</c> respectively when querying DOM properties).
/// Finally, 'boolean' attribute values are returned with either the string <c>true</c> if present or <see langword="null"/> if not.
/// </para>
/// </remarks>
/// <param name="class">The name of the class to query.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> NotClass(string @class)
=> CreatePrototype(new AttributeQuery(AttributeQuery.ClassAttribute), Spec.Func<string>(x => !HasClass(x, @class)), t => $"{t.Name} does not have the class '{@class}'");
/// <summary>
/// Creates a query predicate for the presence of all the specified HTML classes (amongst the class attribute's values).
/// </summary>
/// <remarks>
/// <para>
/// Note that this specification makes use of Selenium's <c>GetAttribute</c> method.
/// The behaviour of that method in fact queries the DOM property of the element first, before querying the HTML attribute.
/// It also has special handling for certain attributes such as <c>class</c> & <c>readonly</c> attributes (substituting them with <c>className</c>
/// and <c>readOnly</c> respectively when querying DOM properties).
/// Finally, 'boolean' attribute values are returned with either the string <c>true</c> if present or <see langword="null"/> if not.
/// </para>
/// </remarks>
/// <param name="classes">The names of the classes to query.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> AllClasses(params string[] classes)
=> CreatePrototype(new AttributeQuery(AttributeQuery.ClassAttribute),
Spec.Func<string>(x => classes.All(@class => HasClass(x, @class))),
t => $"{t.Name} has all the classes {string.Join(", ", classes.Select(c => $"'{c}'"))}");
/// <summary>
/// Gets a value indicating whether the <paramref name="attributeValue"/> (representing an HTML <c>class</c> attribute)
/// contains the specified <paramref name="class"/>.
/// </summary>
/// <param name="attributeValue">The HTML class attribute value.</param>
/// <param name="class">The class for which to search.</param>
/// <returns><see langword="true"/> if the class is present; otherwise <see langword="false"/>.</returns>
static bool HasClass(string attributeValue, string @class)
{
return Regex.IsMatch(attributeValue,
@"\b" + Regex.Escape(@class) + @"\b",
RegexOptions.CultureInvariant,
// DoS prevention: 20ms is more than enough, given this should be a simple regex.
TimeSpan.FromMilliseconds(20));
}
/// <summary>
/// Creates a query predicate based on whether or not the element is clickable.
/// </summary>
/// <param name="predicate">The predicate to apply to the element's "clickability".</param>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> Clickability(Func<bool,bool> predicate)
=> CreatePrototype(new ClickableQuery(), Spec.Func<bool>(x => predicate(x)), t => $"{t.Name} has clickability matching a predicate");
/// <summary>
/// Creates a query predicate based on whether or not the element is clickable.
/// </summary>
/// <param name="value">The value to compare against the element's "clickability".</param>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> Clickability(bool value)
=> CreatePrototype(new ClickableQuery(), Spec.Func<bool>(x => x == value), t => $"{t.Name} {(value ? "is clickable" : "is not clickable")}");
/// <summary>
/// Creates a query predicate based on whether the element is clickable.
/// </summary>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> Clickable() => Clickability(true);
/// <summary>
/// Creates a query predicate based on whether the element is clickable.
/// </summary>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> NotClickable() => Clickability(false);
/// <summary>
/// Creates a query predicate based on a specified CSS property and a predicate for the property's value.
/// </summary>
/// <param name="propertyName">The name of the property to query.</param>
/// <param name="predicate">The predicate to apply to the property value.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> CssProperty(string propertyName, Func<string,bool> predicate)
=> CreatePrototype(new CssPropertyQuery(propertyName), Spec.Func(predicate), t => $"{t.Name} has the CSS property '{propertyName}' matching a predicate");
/// <summary>
/// Creates a query predicate based on a specified CSS property and a desired value for that property.
/// </summary>
/// <param name="propertyName">The name of the property to query.</param>
/// <param name="value">The value to compare against the property value.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> CssProperty(string propertyName, string value)
=> CreatePrototype(new CssPropertyQuery(propertyName), Spec.Func<string>(x => x == value), t => $"{t.Name} has the CSS property '{propertyName}' with value '{value}'");
/// <summary>
/// Creates a query predicate based on the element's location (its top-left corner).
/// </summary>
/// <param name="predicate">The predicate to apply to the element's location.</param>
/// <returns>A <see cref="QueryPredicatePrototype{Point}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<Point> Location(Func<Point,bool> predicate)
=> CreatePrototype(new LocationQuery(), Spec.Func(predicate), t => $"{t.Name} has a page location matching a predicate");
/// <summary>
/// Creates a query predicate based on the element's location (its top-left corner).
/// </summary>
/// <param name="value">The value to compare against the element's location.</param>
/// <returns>A <see cref="QueryPredicatePrototype{Point}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<Point> Location(Point value)
=> CreatePrototype(new LocationQuery(), Spec.Func<Point>(x => x == value), t => $"{t.Name} has a page location equal to {value}");
/// <summary>
/// Creates a query predicate based on the element's size (width & height in pixels).
/// </summary>
/// <param name="predicate">The predicate to apply to the element's size.</param>
/// <returns>A <see cref="QueryPredicatePrototype{Size}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<Size> Size(Func<Size,bool> predicate)
=> CreatePrototype(new SizeQuery(), Spec.Func(predicate), t => $"{t.Name} has a pixel size matching a predicate");
/// <summary>
/// Creates a query predicate based on the element's size (width & height in pixels).
/// </summary>
/// <param name="value">The value to compare against the element's size.</param>
/// <returns>A <see cref="QueryPredicatePrototype{Size}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<Size> Size(Size value)
=> CreatePrototype(new SizeQuery(), Spec.Func<Size>(x => x == value), t => $"{t.Name} has a pixel size equal to {value}");
/// <summary>
/// Creates a query predicate based on the element's text content.
/// </summary>
/// <remarks>
/// <para>
/// When reading text from the web browser, this predicate will trim leading/trailing whitespace from that text before comparing it.
/// This is because some browsers (Safari)
/// include whitespace at the beginning/end of text read from the browser, which isn't visible to the end user. This is typically the
/// space which is inherent in the markup, but which browsers ignore when actually displaying content.
/// </para>
/// <para>
/// Trimming it by default ensures that Screenplay reproduces functionality reliably cross-browser.
/// If this causes an issue and you would like the leading/trailing whitespace included the use
/// <see cref="TextWithoutTrimmingWhitespace(string)"/> instead.
/// Note that you may see different results in browsers which include leading/trailing whitespace anyway.
/// </para>
/// </remarks>
/// <param name="predicate">The predicate to apply to the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Text(Func<string,bool> predicate)
=> CreatePrototype(new TextQuery(), Spec.Func(predicate), t => $"{t.Name} has text matching a predicate");
/// <summary>
/// Creates a query predicate based on the element's text content.
/// </summary>
/// <remarks>
/// <para>
/// When reading text from the web browser, this predicate will trim leading/trailing whitespace from that text before comparing it.
/// This is because some browsers (Safari)
/// include whitespace at the beginning/end of text read from the browser, which isn't visible to the end user. This is typically the
/// space which is inherent in the markup, but which browsers ignore when actually displaying content.
/// </para>
/// <para>
/// Trimming it by default ensures that Screenplay reproduces functionality reliably cross-browser.
/// If this causes an issue and you would like the leading/trailing whitespace included the use
/// <see cref="TextWithoutTrimmingWhitespace(string)"/> instead.
/// Note that you may see different results in browsers which include leading/trailing whitespace anyway.
/// </para>
/// </remarks>
/// <param name="value">The value to compare against the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Text(string value)
=> CreatePrototype(new TextQuery(), Spec.Func<string>(x => x == value), t => $"{t.Name} has text equal to '{value}'");
/// <summary>
/// Creates a query predicate based on the element's text content.
/// </summary>
/// <remarks>
/// <para>
/// When reading text from the web browser, this predicate will leave any leading/trailing whitespace in the text
/// without trimming it.
/// Note that you may see different results in browsers which include leading/trailing whitespace anyway.
/// </para>
/// </remarks>
/// <param name="predicate">The predicate to apply to the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> TextWithoutTrimmingWhitespace(Func<string,bool> predicate)
=> CreatePrototype(new TextQuery(false), Spec.Func(predicate), t => $"{t.Name} has text matching a predicate");
/// <summary>
/// Creates a query predicate based on the element's text content, without trimming leading/trailing whitespace.
/// </summary>
/// <remarks>
/// <para>
/// When reading text from the web browser, this predicate will leave any leading/trailing whitespace in the text
/// without trimming it.
/// Note that you may see different results in browsers which include leading/trailing whitespace anyway.
/// </para>
/// </remarks>
/// <param name="value">The value to compare against the element's text.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> TextWithoutTrimmingWhitespace(string value)
=> CreatePrototype(new TextQuery(false), Spec.Func<string>(x => x == value), t => $"{t.Name} has text equal to '{value}'");
/// <summary>
/// Creates a query predicate based on the element's DOM <c>value</c>.
/// </summary>
/// <param name="predicate">The predicate to apply to the element's DOM value.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Value(Func<string,bool> predicate)
=> CreatePrototype(new ValueQuery(), Spec.Func(predicate), t => $"{t.Name} has a DOM value matching a predicate");
/// <summary>
/// Creates a query predicate based on the element's DOM <c>value</c>.
/// </summary>
/// <param name="value">The value to compare against the element's DOM value.</param>
/// <returns>A <see cref="QueryPredicatePrototype{String}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<string> Value(string value)
=> CreatePrototype(new ValueQuery(), Spec.Func<string>(x => x == value), t => $"{t.Name} has a DOM value equal to '{value}'");
/// <summary>
/// Creates a query predicate based on whether or not the element is visible.
/// </summary>
/// <param name="predicate">The predicate to apply to the element's visibility.</param>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> Visibility(Func<bool,bool> predicate)
=> CreatePrototype(new VisibilityQuery(), Spec.Func(predicate), t => $"{t.Name} has visibility matching a predicate");
/// <summary>
/// Creates a query predicate based on whether or not the element is visible.
/// </summary>
/// <param name="value">The value to compare against the element's visibility.</param>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> Visibility(bool value)
=> CreatePrototype(new VisibilityQuery(), Spec.Func<bool>(x => x == value), t => $"{t.Name} {(value ? "is visible" : "is not visible")}");
/// <summary>
/// Creates a query predicate based on whether the element is visible.
/// </summary>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> Visible() => Visibility(true);
/// <summary>
/// Creates a query predicate based on whether the element is not visible.
/// </summary>
/// <returns>A <see cref="QueryPredicatePrototype{Boolean}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<bool> NotVisible() => Visibility(false);
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, which are selected.
/// </summary>
/// <param name="predicate">The predicate to apply to the element's selected options.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> SelectedOptions(Func<IReadOnlyList<Option>,bool> predicate)
=> CreatePrototype(new OptionsQuery(excludeSelectedOptions: true), Spec.Func(predicate), t => $"{t.Name} has selected options matching a predicate");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, which are selected.
/// </summary>
/// <remarks>
/// <para>
/// This specification will match the element if its selected options are precisely those specified by <paramref name="optionTexts"/>.
/// The order of the options does not matter, but they must match exactly.
/// This method identifies the options by their display text.
/// </para>
/// </remarks>
/// <param name="optionTexts">The display text of the options to match against.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> SelectedOptionsWithText(params string[] optionTexts)
=> CreatePrototype(new OptionsQuery(excludeUnselectedOptions: true),
Spec.Func<IReadOnlyList<Option>>(x => new HashSet<string>(x.Select(o => o.Text)).SetEquals(optionTexts)),
t => $"{t.Name} has selected options with the text values {string.Join(", ", optionTexts.Select(ot => $"'{ot}'"))}");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, which are selected.
/// </summary>
/// <remarks>
/// <para>
/// This specification will match the element if its selected options are precisely those specified by <paramref name="optionValues"/>.
/// The order of the options does not matter, but they must match exactly.
/// This method identifies the options by their DOM value.
/// </para>
/// </remarks>
/// <param name="optionValues">The DOM values of the options to match against.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> SelectedOptionsWithValue(params string[] optionValues)
=> CreatePrototype(new OptionsQuery(excludeUnselectedOptions: true),
Spec.Func<IReadOnlyList<Option>>(x => new HashSet<string>(x.Select(o => o.Value)).SetEquals(optionValues)),
t => $"{t.Name} has selected options with the DOM values {string.Join(", ", optionValues.Select(ov => $"'{ov}'"))}");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, which are not selected.
/// </summary>
/// <param name="predicate">The predicate to apply to the element's unselected options.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> UnselectedOptions(Func<IReadOnlyList<Option>,bool> predicate)
=> CreatePrototype(new OptionsQuery(excludeSelectedOptions : true),
Spec.Func(predicate),
t => $"{t.Name} has unselected options matching a predicate");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, which are not selected.
/// </summary>
/// <remarks>
/// <para>
/// This specification will match the element if its unselected options are precisely those specified by <paramref name="optionTexts"/>.
/// The order of the options does not matter, but they must match exactly.
/// This method identifies the options by their display text.
/// </para>
/// </remarks>
/// <param name="optionTexts">The display text of the options to match against.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> UnselectedOptionsWithText(params string[] optionTexts)
=> CreatePrototype(new OptionsQuery(excludeSelectedOptions: true),
Spec.Func<IReadOnlyList<Option>>(x => new HashSet<string>(x.Select(o => o.Text)).SetEquals(optionTexts)),
t => $"{t.Name} has unselected options with the text values {string.Join(", ", optionTexts.Select(ot => $"'{ot}'"))}");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, which are not selected.
/// </summary>
/// <remarks>
/// <para>
/// This specification will match the element if its unselected options are precisely those specified by <paramref name="optionValues"/>.
/// The order of the options does not matter, but they must match exactly.
/// This method identifies the options by their DOM value.
/// </para>
/// </remarks>
/// <param name="optionValues">The DOM values of the options to match against.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> UnselectedOptionsWithValue(params string[] optionValues)
=> CreatePrototype(new OptionsQuery(excludeSelectedOptions: true),
Spec.Func<IReadOnlyList<Option>>(x => new HashSet<string>(x.Select(o => o.Value)).SetEquals(optionValues)),
t => $"{t.Name} has unselected options with the values {string.Join(", ", optionValues.Select(ov => $"'{ov}'"))}");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, regardless of their selected state.
/// </summary>
/// <param name="predicate">The predicate to apply to the element's options.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> Options(Func<IReadOnlyList<Option>,bool> predicate)
=> CreatePrototype(new OptionsQuery(), Spec.Func(predicate), t => $"{t.Name} has available options matching a predicate");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, regardless of their selected state.
/// </summary>
/// <remarks>
/// <para>
/// This specification will match the element if its options are precisely those specified by <paramref name="optionTexts"/>.
/// The order of the options does not matter, but they must match exactly.
/// This method identifies the options by their display text.
/// </para>
/// </remarks>
/// <param name="optionTexts">The display text of the options to match against.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> OptionsWithText(params string[] optionTexts)
=> CreatePrototype(new OptionsQuery(),
Spec.Func<IReadOnlyList<Option>>(x => new HashSet<string>(x.Select(o => o.Text)).SetEquals(optionTexts)),
t => $"{t.Name} has available options with the text values {string.Join(", ", optionTexts.Select(ot => $"'{ot}'"))}");
/// <summary>
/// Creates a query predicate based on the <c><option></c> elements, which are children of the current element, regardless of their selected state.
/// </summary>
/// <remarks>
/// <para>
/// This specification will match the element if its options are precisely those specified by <paramref name="optionValues"/>.
/// The order of the options does not matter, but they must match exactly.
/// This method identifies the options by their DOM value.
/// </para>
/// </remarks>
/// <param name="optionValues">The DOM values of the options to match against.</param>
/// <returns>A <see cref="QueryPredicatePrototype{IReadOnlyList}"/>, which may be converted to a full predicate.</returns>
public QueryPredicatePrototype<IReadOnlyList<Option>> OptionsWithValue(params string[] optionValues)
=> CreatePrototype(new OptionsQuery(),
Spec.Func<IReadOnlyList<Option>>(x => new HashSet<string>(x.Select(o => o.Value)).SetEquals(optionValues)),
t => $"{t.Name} has available options with the values {string.Join(", ", optionValues.Select(ov => $"'{ov}'"))}");
QueryPredicatePrototype<T> CreatePrototype<T>(IQuery<T> query,
ISpecificationFunction<T> specification,
Func<ITarget, string> summaryCreator)
{
return new QueryPredicatePrototype<T>(specification, query, target, summaryCreator, multiElement);
}
/// <summary>
/// Initializes a new instance of the <see cref="QueryPredicatePrototypeBuilder"/> class for creating instances of
/// <see cref="QueryPredicatePrototype{TQueryable}"/> which are suitable for use as either <see cref="WaitUntilPredicate{Boolean}"/>
/// or <see cref="ISpecificationFunction{SeleniumElement}"/>.
/// </summary>
/// <param name="target">The target element for the queries.</param>
/// <param name="multiElement">If set to <see langword="true"/> then this builder will get a predicate which works
/// for targets which represent a collection of HTML elements; otherwise it will get a predicate for a single HTML
/// element.</param>
public QueryPredicatePrototypeBuilder(ITarget target, bool multiElement)
{
this.target = target ?? throw new ArgumentNullException(nameof(target));
this.multiElement = multiElement;
}
}
}