@@ -11,6 +11,10 @@ module [
11
11
getAttribute,
12
12
getAttributeOrEmpty,
13
13
getPropertyOrEmpty,
14
+ findElement,
15
+ findElements,
16
+ findSingleElement,
17
+ tryFindElement,
14
18
]
15
19
16
20
import Internal exposing [Element ]
@@ -272,3 +276,157 @@ handleElementError = \err ->
272
276
when err is
273
277
e if e |> Str . startsWith " WebDriverElementNotFoundError" -> ElementNotFound (e |> Str . dropPrefix " WebDriverElementNotFoundError::" )
274
278
e -> WebDriverError e
279
+
280
+ ## Supported locator strategies
281
+ ##
282
+ ## `Css Str` - e.g. Css ".my-button-class"
283
+ ##
284
+ ## `TestId Str` - e.g. TestId "button" => Css "[data-testid=\"button\"]"
285
+ ##
286
+ ## `XPath Str` - e.g. XPath "/bookstore/book[price>35]/price"
287
+ ##
288
+ ## `LinkText Str` - e.g. LinkText "Examples" in <a href="/examples-page">Examples</a>
289
+ ##
290
+ ## `PartialLinkText Str` - e.g. PartialLinkText "Exam" in <a href="/examples-page">Examples</a>
291
+ ##
292
+ Locator : [
293
+ # !WARNING this code is duplicated in `Browser` module
294
+ Css Str ,
295
+ TestId Str ,
296
+ XPath Str ,
297
+ LinkText Str ,
298
+ PartialLinkText Str ,
299
+ ]
300
+
301
+ # !WARNING this code is duplicated in `Browser` module
302
+ getLocator : Locator -> (Str , Str )
303
+ getLocator = \locator ->
304
+ when locator is
305
+ Css cssSelector -> (" css selector" , cssSelector)
306
+ # TODO - script injection
307
+ TestId id -> (" css selector" , " [data-testid=\" $( id) \" ]" )
308
+ LinkText text -> (" link text" , text)
309
+ PartialLinkText text -> (" partial link text" , text)
310
+ # Tag tag -> ("tag name", tag)
311
+ XPath path -> (" xpath" , path)
312
+
313
+ ## Find an `Element` inside the tree of another `Element` in the `Browser`.
314
+ ##
315
+ ## When there are more than 1 elements, then the first will
316
+ ## be returned.
317
+ ##
318
+ ## See supported locators at `Locator`.
319
+ ##
320
+ ## ```
321
+ ## # find the html element with a css selector "#my-id"
322
+ ## button = element |> Element.findElement! (Css "#my-id")
323
+ ## ```
324
+ ##
325
+ ## ```
326
+ ## # find the html element with a css selector ".my-class"
327
+ ## button = element |> Element.findElement! (Css ".my-class")
328
+ ## ```
329
+ ##
330
+ ## ```
331
+ ## # find the html element with an attribute [data-testid="my-element"]
332
+ ## button = element |> Element.findElement! (TestId "my-element")
333
+ ## ```
334
+ findElement : Element , Locator -> Task Element [WebDriverError Str , ElementNotFound Str ]
335
+ findElement = \element, locator ->
336
+ { sessionId, elementId } = Internal . unpackElementData element
337
+ (using, value) = getLocator locator
338
+
339
+ newElementId = Effect . elementFindElement sessionId elementId using value |> Task . mapErr ! handleElementError
340
+
341
+ selectorText = " $( locator |> Inspect . toStr )"
342
+
343
+ Internal . packElementData { sessionId, elementId: newElementId, selectorText } |> Task . ok
344
+
345
+ ## Find an `Element` inside the tree of another `Element` in the `Browser`.
346
+ ##
347
+ ## This function returns a `[Found Element, NotFound]` instead of an error
348
+ ## when element is not found.
349
+ ##
350
+ ## When there are more than 1 elements, then the first will
351
+ ## be returned.
352
+ ##
353
+ ## See supported locators at `Locator`.
354
+ ##
355
+ ## ```
356
+ ## maybeButton = element |> Element.tryFindElement! (Css "#submit-button")
357
+ ##
358
+ ## when maybeButton is
359
+ ## NotFound -> Stdout.line! "Button not found"
360
+ ## Found el ->
361
+ ## buttonText = el |> Element.getText!
362
+ ## Stdout.line! "Button found with text: $(buttonText)"
363
+ ## ```
364
+ tryFindElement : Element , Locator -> Task [Found Element , NotFound ] [WebDriverError Str , ElementNotFound Str ]
365
+ tryFindElement = \element, locator ->
366
+ findElement element locator
367
+ |> Task . map Found
368
+ |> Task . onErr \err ->
369
+ when err is
370
+ ElementNotFound _ -> Task . ok NotFound
371
+ other -> Task . err other
372
+
373
+ ## Find an `Element` inside the tree of another `Element` in the `Browser`.
374
+ ##
375
+ ## This function will fail if the element is not found - `ElementNotFound Str`
376
+ ##
377
+ ## This function will fail if there are more than 1 element - `AssertionError Str`
378
+ ##
379
+ ##
380
+ ## See supported locators at `Locator`.
381
+ ##
382
+ ## ```
383
+ ## button = element |> Element.findSingleElement! (Css "#submit-button")
384
+ ## ```
385
+ findSingleElement : Element , Locator -> Task Element [AssertionError Str , ElementNotFound Str , WebDriverError Str ]
386
+ findSingleElement = \element, locator ->
387
+ { selectorText: parentElementSelectorText } = Internal . unpackElementData element
388
+ elements = findElements! element locator
389
+ when elements |> List . len is
390
+ 0 ->
391
+ (_, value) = getLocator locator
392
+ Task.err (ElementNotFound " element with selector $(value) was not found in element $(parentElementSelectorText)" )
393
+
394
+ 1 ->
395
+ elements
396
+ |> List.first
397
+ |> Result.onErr \_ -> crash " just checked - there is 1 element in the list"
398
+ |> Task.fromResult
399
+
400
+ n ->
401
+ (_, value) = getLocator locator
402
+ Task.err (AssertionError " expected to find only 1 element with selector \" $( value) \" , but found $( n |> Num . toStr )")
403
+
404
+ ## Find all `Elements` inside the tree of another `Element` in the `Browser`.
405
+ ##
406
+ ## When there are no elements found, then the list will be empty.
407
+ ##
408
+ ## See supported locators at `Locator`.
409
+ ##
410
+ ## ```
411
+ ## # find all <li> elements in #my-list in the DOM tree of **element**
412
+ ## listItems = element |> Element.findElements! (Css "#my-list li")
413
+ ## ```
414
+ ##
415
+ findElements : Element , Locator -> Task (List Element ) [WebDriverError Str, ElementNotFound Str]
416
+ findElements = \element, locator ->
417
+ { sessionId, elementId: parentElementId } = Internal.unpackElementData element
418
+ (using, value) = getLocator locator
419
+
420
+ result = Effect.elementFindElements sessionId parentElementId using value |> Task.mapErr handleElementError |> Task.result!
421
+
422
+ selectorText = " $(locator |> Inspect . toStr )"
423
+
424
+ when result is
425
+ Ok elementIds ->
426
+ elementIds
427
+ |> List . map \elementId ->
428
+ Internal . packElementData { sessionId, elementId, selectorText }
429
+ |> Task . ok
430
+
431
+ Err (ElementNotFound _ ) -> Task . ok []
432
+ Err err -> Task . err err
0 commit comments