From 7f593a0c2f8c9bf5d03c32bca66fe28fd87eeea5 Mon Sep 17 00:00:00 2001 From: Asaf Korem Date: Tue, 12 Mar 2024 12:41:00 +0200 Subject: [PATCH 1/2] docs(webview): revert to previous version. --- docs/api/webviews.md | 395 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 319 insertions(+), 76 deletions(-) diff --git a/docs/api/webviews.md b/docs/api/webviews.md index 430f09e1b1..13f8a95f07 100644 --- a/docs/api/webviews.md +++ b/docs/api/webviews.md @@ -1,130 +1,373 @@ -# Web Views +# Web views -Web views are native components that render content not natively supported by the platform, like web pages or PDF documents. -However, elements within web views are not native components, making direct interaction through Detox challenging. -To address this, Detox offers a suite of matchers, actions, and expectations designed for interacting with content inside web views. +:::caution Note +Detox supports testing web views on **Android** only. We are working on adding support for iOS apps as well. +::: -## Locating Web View Elements +A web view is a native component that displays content not available in a native format, such as a web page or a PDF document. -### Single Web View Scenario +Elements inside web views, however, are not native components, so Detox cannot interact with them the usual way. +That's why Detox provides a set of matchers, actions, and expectations to allow you to interact with the content inside web views. -When dealing with a single web view on the screen, use the `web.element()` function with web view matchers to locate elements within it. Once an element is located, you can apply web view actions and expectations to it. +## Locating web view elements -```javascript -const innerElement = web.element(by.web.id('inner_element_identifier')); +### `web.element(matcher)` + +In the most common case, you will have a single web view on the screen, so you can use `web.element()` function with [web view matchers] to reference elements inside it. After you have a reference to a web element, you can use the [web view actions] and [web view expectations] on it, e.g.: + +```js +const innerElement = web.element(by.web.id('inner_element_identifier')) await expect(innerElement).toHaveText('Hello World!'); ``` -This example demonstrates locating an element by its HTML `id` attribute and verifying its text content. +The code above finds an inner element by HTML `id` attribute, and expects it to have a specific text. -### Multiple Web Views Scenario +### `web(nativeMatcher).element(matcher)` -For screens with multiple web views, first identify the specific web view using a **[native matcher]**. Then, locate the element within that web view. +If you have multiple web views on the screen, you must locate a specific web view first by using a [native matcher][native matchers], e.g.: -```javascript +```js const myWebView = web(by.id('webview_identifier')); -const innerElement = myWebView.element(by.web.id('inner_element_identifier')); +const innerElement = myWebView.element(by.web.id('inner_element_identifier')) await expect(innerElement).toHaveText('Hello World!'); ``` -#### Using `atIndex` (iOS only) - -It is also possible to locate the web view by applying at-index to the web view matcher: - -```javascript -const myWebView = web(by.id('webview_identifier').atIndex(1)); -``` +In the example above: -In this example: - -1. The `web()` function and `by.id()` matcher locate the web view by its accessibility identifier. -2. The `myWebView.element()` method and `by.web.id()` matcher locate an HTML element within the web view. -3. The expectation to verify the element's text is the same as in the single web view scenario. +1. We use `web()` function and [`by.id()`] matcher to locate a web view by its accessibility identifier. +1. We use `myWebView.element()` method and [`by.web.id()`] web matcher to find an HTML element inside that web view. +1. We run the same expectation (to have text) as in the previous example. ## Matchers -Detox provides various matchers for locating elements within a web view: +Web view matchers are used to find elements within a web view: -- `by.web.id(id)` - Matches elements with the specified HTML `id`. -- `by.web.className(className)` - Matches elements with the specified CSS class name. -- `by.web.cssSelector(cssSelector)` - Matches elements with the specified CSS selector. -- `by.web.name(name)` - Matches form input elements with the specified `name` attribute. -- `by.web.xpath(xpath)` - Matches elements with the specified XPath. -- `by.web.href(href)` - Matches elements with the specified `href` attribute. -- `by.web.hrefContains(hrefContains)` - Matches elements containing a specified `href` substring. -- `by.web.tag(tag)` - Matches elements with the specified tag name. -- `atIndex(index)` - Selects the element at a specified index when multiple matches are found. +- [`by.web.id()`] +- [`by.web.className()`] +- [`by.web.cssSelector()`] +- [`by.web.name()`] +- [`by.web.xpath()`] +- [`by.web.href()`] +- [`by.web.hrefContains()`] +- [`by.web.tag()`] +- [`atIndex()`] -### Example Matchers +### `by.web.id(id)` -Here are examples of using some of the matchers: +Match elements with the specified accessibility identifier. -```javascript -// Match by HTML ID +```js web.element(by.web.id('identifier')); +``` + +### `by.web.className(className)` + +Match elements with the specified CSS class name. -// Match by CSS Class Name +```js web.element(by.web.className('className')); +``` + +### `by.web.cssSelector(cssSelector)` -// Match by CSS Selector +Match elements with the specified CSS selector. + +```js web.element(by.web.cssSelector('#cssSelector')); +``` -// Match with index -web.element(by.web.id('identifier').atIndex(1)); +### `by.web.name(name)` + +Match form input elements with the specified [`name` attribute][name]. + +```js +web.element(by.web.name('name')); ``` +### `by.web.xpath(xpath)` + +Match elements with the specified XPath. + +```js +web.element(by.web.xpath('//*[@id="testingh1-1"]')); +``` + +### `by.web.href(href)` + +Match elements with the specified `href`. + +```js +web.element(by.web.href('https://wix.com')); +``` + +### `by.web.hrefContains(href)` + +Match elements that contain the specified `href`. + +```js +web.element(by.web.hrefContains('wix')); +``` + +### `by.web.tag(tag)` + +Match elements with the specified tag. + +```js +web.element(by.web.tag('h1')); +``` + +### `atIndex(index)` + +Choose the element at the specified index. + +```js +web.element(by.web.tag('h1').atIndex(1)); +``` + +Use it sparingly for those rare cases when you cannot make your matcher less ambiguous, so it returns one element only. + ## Actions -Actions allow you to interact with elements within a web view. Available actions include: - -- `tap()` - Taps the element. -- `typeText(text[, isContentEditable])` - Types text into the element. **Android:** the `isContentEditable` flag indicates if the element is [content-editable], defaulting to `false`. -- `replaceText(text)` - Replaces the element's text. **Android:** not supported for content-editable elements. -- `clearText()` - Clears the element's text. **Android:** not supported for content-editable elements. -- `selectAllText()` - Selects all text of the element. **Android:** supported only for content-editable elements. -- `getText()` - Retrieves the element's text. -- `scrollToView()` - Scrolls until the element is visible at the top of the viewport. -- `focus()` - Focuses on the element. -- `moveCursorToEnd()` - Moves the cursor to the end of the element's content. **Android:** supported only for content-editable elements. -- `runScript(script[, args])` - Executes a JavaScript script on the element. You can pass a function or a string to be executed in the context of the web view, and the result will be returned. **Note:** Can only be called from an inner element. -- `getCurrentUrl()` - Retrieves the current URL of the web view. **Note:** Can only be called from an inner element. -- `getTitle()` - Retrieves the title of the web view. **Note:** Can only be called from an inner element. - -### Action Examples - -```javascript -// Tap an element +Web view actions are used to interact with elements within a web view: + +- [`tap()`] +- [`typeText()`] +- [`replaceText()`] +- [`clearText()`] +- [`selectAllText()`] +- [`getText()`] +- [`scrollToView()`] +- [`focus()`] +- [`moveCursorToEnd()`] +- [`runScript()`] +- [`getCurrentUrl()`] +- [`getTitle()`] + +### `tap()` + +Tap the element. + +```js await web.element(by.web.id('identifier')).tap(); +``` -// Type text into an element +### `typeText(text[, isContentEditable])` + +Type the specified text into the element. + +`isContentEditable` is an optional parameter that indicates whether the element should be a [content-editable] (`contenteditable`) element, and defaults to `false`. + +```js await web.element(by.web.id('identifier')).typeText('Hello World!'); +``` + +### `replaceText(text)` + +Replace the text of the element with the specified text. + +:::note +This action is currently not supported for content-editable elements. +::: + +```js +await web.element(by.web.id('identifier')).replaceText('Hello World!'); +``` + +### `clearText()` + +Clear the text of the element. + +:::note +This action is currently not supported for content-editable elements. +::: + +```js +await web.element(by.web.id('identifier')).clearText(); +``` + +### `selectAllText()` + +Select all the text of the element. + +:::note +This action is currently supported for content-editable elements only. +::: + +```js +await web.element(by.web.id('identifier')).selectAllText(); +``` + +### `getText()` + +Get the text of the element. + +```js +const text = await web.element(by.web.id('identifier')).getText(); +``` + +### `scrollToView()` -// Run a JavaScript script on an element -await web.element(by.web.id('identifier')).runScript('(element) => element.click()'); +Scroll to the element until its top is at the top of the viewport. -// Run a JavaScript script on an element with paramaters +```js +await web.element(by.web.id('identifier')).scrollToView(); +``` + +### `focus()` + +Focus on the element. + +```js +await web.element(by.web.id('identifier')).focus(); +``` + +### `moveCursorToEnd()` + +Move the input cursor to the end of the element's content. + +:::note +This action is currently supported for content-editable elements only. +::: + +```js +await web.element(by.web.id('identifier')).moveCursorToEnd(); +``` + +### `runScript(script[, args])` + +Run the specified script on the element. +The script should be a string that contains a valid JavaScript function. +It will be called with that element as the first argument: + +```js +const webElement = web.element(by.web.id('identifier')); +await webElement.runScript('(el) => el.click()'); +``` + +For convenience, you can pass a function instead of a string, but please note that this will not work if the function uses any variables from the outer scope: + +The script can accept additional arguments and return a value. Make sure the values are primitive types or serializable objects, as they will be converted to JSON and back: + +```js const text = await webElement.runScript(function get(element, property) { return element[property]; }, ['textContent']); ``` +### `getCurrentUrl()` + +Get the current URL of the web view. + +:::note +Although this action returns the URL of the presented web document, it can be called from an inner element only (for example, an iframe id or the HTML) and not from the root native web view element itself. +::: + +```js +const url = await web.element(by.web.id('identifier')).getCurrentUrl(); +``` + +### `getTitle()` + +Get the title of the web view. + +:::note +Although this action returns the title of the presented web document, it can be called from an inner element only (for example, an iframe id or the HTML) and not from the root native web view element itself. +::: + +```js +const title = await web.element(by.web.id('identifier')).getTitle(); +``` + ## Expectations -Expectations are assertions on the state of elements within a web view: +Web view expectations are used to assert the state of elements within a web view: -- `toHaveText(text)` - Asserts the element contains the specified text. -- `toExist()` - Asserts the element exists. -- `not` - Negates the expectation, e.g., `.not.toHaveText('text')`. +- [`toHaveText()`] +- [`toExist()`] +- [`not`] -### Expectation Examples +### `toHaveText(text)` -```javascript -// Assert an element has specific text +Assert that the element has the specified text. + +```js await expect(web.element(by.web.id('identifier'))).toHaveText('Hello World!'); +``` -// Assert an element exists +### `toExist()` + +Assert that the element exists. + +```js await expect(web.element(by.web.id('identifier'))).toExist(); ``` -[native matcher]: matchers.md +### `not` + +Negate the expectation. + +```js +await expect(web.element(by.web.id('identifier'))).not.toHaveText('Hello World!'); +``` + +[native matchers]: matchers.md + +[`by.id()`]: matchers.md#byidid + +[web view matchers]: webviews.md#matchers + +[web view actions]: webviews.md#actions + +[web view expectations]: webviews.md#expectations + +[`by.web.id()`]: webviews.md#bywebidid + +[`by.web.className()`]: webviews.md#bywebclassnameclassname + +[`by.web.cssSelector()`]: webviews.md#bywebcssselectorcssselector + +[`by.web.name()`]: webviews.md#bywebnamename + +[`by.web.xpath()`]: webviews.md#bywebxpathxpath + +[`by.web.href()`]: webviews.md#bywebhrefhref + +[`by.web.hrefContains()`]: webviews.md#bywebhrefcontainshref + +[`by.web.tag()`]: webviews.md#bywebtagtag + +[`atIndex()`]: webviews.md#atindexindex + +[`tap()`]: webviews.md#tap + +[`typeText()`]: webviews.md#typetexttext-iscontenteditable + [content-editable]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/contentEditable + +[`replaceText()`]: webviews.md#replacetexttext + +[`clearText()`]: webviews.md#cleartext + +[`selectAllText()`]: webviews.md#selectalltext + +[`getText()`]: webviews.md#gettext + +[`scrollToView()`]: webviews.md#scrolltoview + +[`focus()`]: webviews.md#focus + +[`moveCursorToEnd()`]: webviews.md#movecursortoend + +[name]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#name + +[`runScript()`]: webviews.md#runscriptscript-args + +[`getCurrentUrl()`]: webviews.md#getcurrenturl + +[`getTitle()`]: webviews.md#gettitle + +[`toHaveText()`]: webviews.md#tohavetexttext + +[`toExist()`]: webviews.md#toexist + +[`not`]: webviews.md#not From e3190e39243e2647dfbd2100c3e6d12d30d99ecf Mon Sep 17 00:00:00 2001 From: Asaf Korem Date: Tue, 12 Mar 2024 14:52:21 +0200 Subject: [PATCH 2/2] docs(webviews): update with recent changes. --- docs/api/webviews.md | 133 ++++++++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 34 deletions(-) diff --git a/docs/api/webviews.md b/docs/api/webviews.md index 13f8a95f07..f3ee37ce8b 100644 --- a/docs/api/webviews.md +++ b/docs/api/webviews.md @@ -1,34 +1,33 @@ -# Web views +# Web Views -:::caution Note -Detox supports testing web views on **Android** only. We are working on adding support for iOS apps as well. -::: - -A web view is a native component that displays content not available in a native format, such as a web page or a PDF document. - -Elements inside web views, however, are not native components, so Detox cannot interact with them the usual way. -That's why Detox provides a set of matchers, actions, and expectations to allow you to interact with the content inside web views. +Web views are native components that render content not natively supported by the platform, like web pages or PDF documents. +However, elements within web views are not native components, making direct interaction through Detox challenging. +To address this, Detox offers a suite of matchers, actions, and expectations designed for interacting with content inside web views. -## Locating web view elements +## Locating Elements in Web Views ### `web.element(matcher)` -In the most common case, you will have a single web view on the screen, so you can use `web.element()` function with [web view matchers] to reference elements inside it. After you have a reference to a web element, you can use the [web view actions] and [web view expectations] on it, e.g.: +In cases where there's only one web view present on the screen, you may use the `web.element()` function, paired with [web view matchers], to reference elements within the web view. +Upon obtaining the element reference, you can utilize web view actions and expectations on the webView element. ```js -const innerElement = web.element(by.web.id('inner_element_identifier')) +const innerElement = web.element(by.web.id('inner_element_identifier')); await expect(innerElement).toHaveText('Hello World!'); ``` -The code above finds an inner element by HTML `id` attribute, and expects it to have a specific text. +The operation above explains how to locate an inner element using its HTML id attribute and how to verify its text content. ### `web(nativeMatcher).element(matcher)` +For screens with several web views, it's first necessary to identify a specific web view using a native matcher. +Following that, you can locate the element within the identified web view. + If you have multiple web views on the screen, you must locate a specific web view first by using a [native matcher][native matchers], e.g.: ```js const myWebView = web(by.id('webview_identifier')); -const innerElement = myWebView.element(by.web.id('inner_element_identifier')) +const innerElement = myWebView.element(by.web.id('inner_element_identifier')); await expect(innerElement).toHaveText('Hello World!'); ``` @@ -38,6 +37,24 @@ In the example above: 1. We use `myWebView.element()` method and [`by.web.id()`] web matcher to find an HTML element inside that web view. 1. We run the same expectation (to have text) as in the previous example. +### `web(nativeMatcher).atIndex(index).element(matcher)` + +If you have multiple web views on the screen and you want to interact with a specific web view, you can use the `atIndex()` method to choose the web view at the specified index. + +```js +const myWebView = web(by.id('webview_identifier').atIndex(1)); +const innerElement = myWebView.element(by.web.id('inner_element_identifier')); +await expect(innerElement).toHaveText('Hello World!'); +``` + +In the example above, we use `atIndex()` to select the second web view on the screen (that has the specified accessibility identifier) and then locate an HTML element inside that web view. + +:::note + +This matcher is available for iOS only. See [this GitHub issue](https://github.com/wix/Detox/issues/4398) on for more information. + +::: + ## Matchers Web view matchers are used to find elements within a web view: @@ -100,6 +117,12 @@ Match elements with the specified `href`. web.element(by.web.href('https://wix.com')); ``` +:::note + +You might face issues with this matcher on Android. Check [this GitHub issue](https://github.com/wix/Detox/issues/4398) for more information. + +::: + ### `by.web.hrefContains(href)` Match elements that contain the specified `href`. @@ -108,6 +131,12 @@ Match elements that contain the specified `href`. web.element(by.web.hrefContains('wix')); ``` +:::note + +You might face issues with this matcher on Android. Check [this GitHub issue](https://github.com/wix/Detox/issues/4398) for more information. + +::: + ### `by.web.tag(tag)` Match elements with the specified tag. @@ -161,42 +190,62 @@ Type the specified text into the element. await web.element(by.web.id('identifier')).typeText('Hello World!'); ``` -### `replaceText(text)` +:::note -Replace the text of the element with the specified text. +The `isContentEditable` parameter is currently necessary for content-editable elements only on Android. + +On iOS, Detox automatically detects content-editable elements regardless of this parameter. -:::note -This action is currently not supported for content-editable elements. ::: +### `replaceText(text)` + +Replace the text of the element with the specified text. + ```js await web.element(by.web.id('identifier')).replaceText('Hello World!'); ``` -### `clearText()` +:::note -Clear the text of the element. +This action is currently not supported for content-editable elements on Android. + +On iOS, Detox automatically detects content-editable elements and replaces their text. -:::note -This action is currently not supported for content-editable elements. ::: +### `clearText()` + +Clear the text of the element. + ```js await web.element(by.web.id('identifier')).clearText(); ``` -### `selectAllText()` +:::note -Select all the text of the element. +This action is currently not supported for content-editable elements on Android. + +On iOS, Detox automatically detects content-editable elements and clears their text. -:::note -This action is currently supported for content-editable elements only. ::: +### `selectAllText()` + +Select all the text of the element. + ```js await web.element(by.web.id('identifier')).selectAllText(); ``` +:::note + +This action is currently supported for content-editable elements only on Android. + +On iOS, Detox can select all the text of any element that supports it (for example, an input element). + +::: + ### `getText()` Get the text of the element. @@ -225,14 +274,18 @@ await web.element(by.web.id('identifier')).focus(); Move the input cursor to the end of the element's content. -:::note -This action is currently supported for content-editable elements only. -::: - ```js await web.element(by.web.id('identifier')).moveCursorToEnd(); ``` +:::note + +This action is currently supported for content-editable elements only on Android. + +On iOS, Detox can move the cursor to the end of any element that supports it (for example, an input element). + +::: + ### `runScript(script[, args])` Run the specified script on the element. @@ -258,20 +311,26 @@ const text = await webElement.runScript(function get(element, property) { Get the current URL of the web view. -:::note -Although this action returns the URL of the presented web document, it can be called from an inner element only (for example, an iframe id or the HTML) and not from the root native web view element itself. -::: - ```js const url = await web.element(by.web.id('identifier')).getCurrentUrl(); ``` +:::note + +Although this action returns the URL of the presented web document, it can be called from an inner element only (for example, an iframe id or the HTML) and not from the root native web view element itself. + +You might face issues with this action on Android. Check [this GitHub issue](https://github.com/wix/Detox/issues/4398) for more information. + +::: + ### `getTitle()` Get the title of the web view. :::note + Although this action returns the title of the presented web document, it can be called from an inner element only (for example, an iframe id or the HTML) and not from the root native web view element itself. + ::: ```js @@ -302,6 +361,12 @@ Assert that the element exists. await expect(web.element(by.web.id('identifier'))).toExist(); ``` +:::note + +You might face issues with this expectation on Android. Check [this GitHub issue](https://github.com/wix/Detox/issues/4398) for more information. + +::: + ### `not` Negate the expectation.