diff --git a/404.html b/404.html index 656247d331..d1a358b9ea 100644 --- a/404.html +++ b/404.html @@ -11,13 +11,13 @@ - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/435645a6.aed80a98.js b/assets/js/435645a6.aed80a98.js new file mode 100644 index 0000000000..b7dc126276 --- /dev/null +++ b/assets/js/435645a6.aed80a98.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2661],{15680:(e,t,n)=>{n.d(t,{xA:()=>m,yg:()=>g});var a=n(96540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function s(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):s(s({},t),e)),n},m=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,l=e.parentName,m=r(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,g=u["".concat(l,".").concat(d)]||u[d]||c[d]||o;return n?a.createElement(g,s(s({ref:t},m),{},{components:n})):a.createElement(g,s({ref:t},m))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,s=new Array(o);s[0]=d;var r={};for(var l in t)hasOwnProperty.call(t,l)&&(r[l]=t[l]);r.originalType=e,r[u]="string"==typeof e?e:i,s[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));const o={},s="Adding test ID's to your components",r={unversionedId:"guide/test-id",id:"version-20.x/guide/test-id",title:"Adding test ID's to your components",description:"This guide was written primarily for React Native apps, but it can be generalized for testing any app, including native apps.",source:"@site/versioned_docs/version-20.x/guide/test-id.md",sourceDirName:"guide",slug:"/guide/test-id",permalink:"/Detox/docs/guide/test-id",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/guide/test-id.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Investigating Failures",permalink:"/Detox/docs/guide/investigating-test-failure"},next:{title:"Parallel Test Execution",permalink:"/Detox/docs/guide/parallel-test-execution"}},l={},p=[{value:"Pass testID to your native components",id:"pass-testid-to-your-native-components",level:2},{value:"Child elements",id:"child-elements",level:3},{value:"Repetitive components",id:"repetitive-components",level:3},{value:"Finding your test ID",id:"finding-your-test-id",level:2},{value:"Test ID naming - Best practices",id:"test-id-naming---best-practices",level:2},{value:"Use a consistent naming system",id:"use-a-consistent-naming-system",level:3},{value:"Use simple names",id:"use-simple-names",level:3},{value:"Dissociate test ID names",id:"dissociate-test-id-names",level:3},{value:"Examples",id:"examples",level:3}],m={toc:p},u="wrapper";function c(e){let{components:t,...o}=e;return(0,i.yg)(u,(0,a.A)({},m,o,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h1",{id:"adding-test-ids-to-your-components"},"Adding test ID's to your components"),(0,i.yg)("admonition",{title:"Note",type:"info"},(0,i.yg)("p",{parentName:"admonition"},"This guide was written primarily for React Native apps, but it can be generalized for testing any app, including native apps.")),(0,i.yg)("p",null,"While ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"view-element matching")," can be done in numerous ways, it is always the best idea to match based on something unique and decoupled, as it ensures that the test code is clear, stable and sustainable over time."),(0,i.yg)("p",null,"We recommend assigning unique test ID's to the elements you're aiming to interact with in your tests, and preferring matching based on those rather than on anything else. Test ID's are the least likely to change over time (compared with raw text, for example), and are locale-agnostic. Furthermore, utilizing unique test ID's across the app not only simplifies the identification and interaction with specific elements but also enhances code navigability, making it easier to locate elements when traversing the codebase."),(0,i.yg)("p",null,"In React Native applications, ",(0,i.yg)("inlineCode",{parentName:"p"},"View")," components have a dedicated ",(0,i.yg)("a",{parentName:"p",href:"https://reactnative.dev/docs/view#testid"},"test ID property")," that can be utilized:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx"},'\n \n Next\n \n\n')),(0,i.yg)("p",null,"For native apps, test ID's can be assigned by setting a value for the following properties:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"iOS:")," ",(0,i.yg)("a",{parentName:"li",href:"https://developer.apple.com/documentation/uikit/uiaccessibilityidentification/1623132-accessibilityidentifier"},(0,i.yg)("inlineCode",{parentName:"a"},"accessibilityIdentifier"))),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"Android:")," Default ",(0,i.yg)("a",{parentName:"li",href:"https://developer.android.com/reference/android/view/View#tags"},"viewTag"))),(0,i.yg)("h2",{id:"pass-testid-to-your-native-components"},"Pass testID to your native components"),(0,i.yg)("p",null,"Passing a ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," to your custom component props has no effect until you forward it down to a native component like ",(0,i.yg)("inlineCode",{parentName:"p"},"")," or ",(0,i.yg)("inlineCode",{parentName:"p"},""),"\nthat implements rendering it as an accessibility identifier in the native component hierarchy:"),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"Pass testID to native component",src:n(86083).A,width:"2116",height:"358"})),(0,i.yg)("p",null,"For example, you have ",(0,i.yg)("inlineCode",{parentName:"p"},"")," and you pass a ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," to it:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourScreen.jsx"',title:'"YourScreen.jsx"'},'function YourScreen() {\n return (\n \n );\n}\n')),(0,i.yg)("p",null,"Make sure that your implementation passes ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," to some React Native component that supports it:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourCustomComponent.jsx"',title:'"YourCustomComponent.jsx"'},"function YourCustomComponent(props) {\n return (\n// highlight-next-line\n \n Some text\n \n );\n}\n")),(0,i.yg)("h3",{id:"child-elements"},"Child elements"),(0,i.yg)("p",null,"If your component has several useful child elements, it is even a better idea to assign them some derived test IDs, e.g.:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourCustomComponent.jsx"',title:'"YourCustomComponent.jsx"'},"function YourCustomComponent(props) {\n return (\n// highlight-next-line\n \n Some text\n \n );\n}\n")),(0,i.yg)("p",null,"That way, you could refer to specific elements in Detox tests via the most basic and least ambiguous ",(0,i.yg)("inlineCode",{parentName:"p"},"by.id")," matchers, e.g.:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-js"},"expect(element(by.id('YourCustomComponent'))).toBeVisible();\nexpect(element(by.id('YourCustomComponent.label'))).toHaveText('Some text');\n")),(0,i.yg)("h3",{id:"repetitive-components"},"Repetitive components"),(0,i.yg)("p",null,"It is highly not recommended to use non-unique ",(0,i.yg)("inlineCode",{parentName:"p"},"testID"),", e.g. when your components get rendered in any sort of repeater or virtualized list:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourScreen.jsx"',title:'"YourScreen.jsx"'},"const ITEMS = [\n { title: 'First Item' },\n { title: 'Second Item' },\n { title: 'Third Item' },\n];\n\nfunction YourScreen() {\n const renderItem = ({ item }) => (\n// highlight-next-line\n \n );\n\n return (\n \n );\n}\n")),(0,i.yg)("p",null,"This would be a violation of accessibility guidelines and unnecessary complication for your test matchers.\nYou\u2019d also have to use extra matchers and ",(0,i.yg)("inlineCode",{parentName:"p"},".atIndex")," clarification:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-js"},"expect(element(by.id('listItem')).atIndex(2)).toHaveText('Third Item');\n")),(0,i.yg)("p",null,"Instead, you could generate a unique ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," for every list item with the ",(0,i.yg)("inlineCode",{parentName:"p"},"index")," property:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx"}," const renderItem = ({ item, index }) => (\n \n );\n")),(0,i.yg)("p",null,"That way, your assertion would become simpler and more deterministic:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-js"},"expect(element(by.id('listItem.3'))).toHaveText('Third Item');\n")),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"testID for repetitive components",src:n(43454).A,width:"2117",height:"624"})),(0,i.yg)("h2",{id:"finding-your-test-id"},"Finding your test ID"),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"Incorrect or absent ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," is a common cause for test failure.\nIf your test can't find your ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," and you can't see it either using tools described below, that usually means you haven't passed it down to this component.\nMake sure you keep forwarding it down until it reaches a native component.")),(0,i.yg)("p",null,"To make sure your ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," is indeed rendered in your app, you can use such tools as MacOS' built-in ",(0,i.yg)("a",{parentName:"p",href:"https://developer.apple.com/documentation/accessibility/inspecting-the-accessibility-of-screens"},"accessibility inspector")," for iOS, and ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-inspector"},"Detox Layout-inspector")," (setup required) for Android."),(0,i.yg)("h2",{id:"test-id-naming---best-practices"},"Test ID naming - Best practices"),(0,i.yg)("p",null,"Test ID's work best when they are unique, simple and concise. Here are our recommendations regarding what rules to follow in terms of naming."),(0,i.yg)("h3",{id:"use-a-consistent-naming-system"},"Use a consistent naming system"),(0,i.yg)("p",null,"Decide upon a system by which test ID's are named, and stick with it."),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"Use a consistent naming convention. An ",(0,i.yg)("inlineCode",{parentName:"p"},"ITEM_NAME_ALL_CAPS")," convention and an ",(0,i.yg)("inlineCode",{parentName:"p"},"ItemNameUpperCamelCase")," are both ok, but ",(0,i.yg)("strong",{parentName:"p"},"don't use them either intermittently nor in conjunction:")),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM_1")," - :white","_","check","_","mark:"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"SiteList_Item1")," - \u274c"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_Item1")," - \u274c"))),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"Consistently apply notations for special items. For example:"),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"A ",(0,i.yg)("inlineCode",{parentName:"li"},"_ROOT")," postfix for screen-root or list-root items (e.g. ",(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT"),")"),(0,i.yg)("li",{parentName:"ul"},"A ",(0,i.yg)("inlineCode",{parentName:"li"},"_BTN")," for buttons / touchable CTA elements"))),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"Apply consistent prefixes as categories in order to introduce a top-level context to the test ID, distinguishing it from similar ones in various places in the app. The name of the associated screen can be useful in that sense. For example: ",(0,i.yg)("inlineCode",{parentName:"p"},"EDIT_PROFILE_SCREEN.DONE_BTN")," is better than just ",(0,i.yg)("inlineCode",{parentName:"p"},"DONE_BTN")," for a button that is inside a user profile editing screen. Also, things such as ",(0,i.yg)("inlineCode",{parentName:"p"},"NAV_TABS."),", ",(0,i.yg)("inlineCode",{parentName:"p"},"TOP_TABS.")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"SIDE_MENU.")," can be used as good context providers.")),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"As explained in the section on passing test ID's to ",(0,i.yg)("em",{parentName:"p"},"child")," elements, drill down to the details of elements via a ",(0,i.yg)("em",{parentName:"p"},"chain of contexts"),'. Given the parent element-group of an element (for example, a card in a feed), use its own test ID as a prefix for the sub-items (e.g. an options "meatballs" / "kebab" CTA or an ',(0,i.yg)("em",{parentName:"p"},"edit")," button). For example:"),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1")," \u21d2",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1.OPTIONS")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1.EDIT_BTN")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1.TITLE")))))),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"In a large-scale, multi-module environment, apply a consistent module identifier as the module's test ID's prefix. For example:"),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"AUTH.LOGIN_SCREEN.EDIT_PASSWORD")," - the ",(0,i.yg)("inlineCode",{parentName:"li"},"AUTH.")," prefix suggests that were are under the context of a module handling Authentication matters.")))),(0,i.yg)("admonition",{type:"tip"},(0,i.yg)("p",{parentName:"admonition"},"Don't hesitate to articulate a well defined conventions manifest that all teams should adhere to.")),(0,i.yg)("h3",{id:"use-simple-names"},"Use simple names"),(0,i.yg)("p",null,"Stick to simple alpha-numeric characters, and simple separators. When it comes to test ID's, there's usually no reason to use special characters or emojis."),(0,i.yg)("p",null,"In addition, use test ID that clearly describe the associated element, but are also concise. For example:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT")," - :white","_","check","_","mark:"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"MAIN_SITE_LIST_WRAPPER_ELEMENT")," - \u274c"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST@ITEM$1")," - \u274c")),(0,i.yg)("h3",{id:"dissociate-test-id-names"},"Dissociate test ID names"),(0,i.yg)("p",null,"Make sure the names you give test ID's are completely decoupled and dissociated from everything else in the system. In particular -"),(0,i.yg)("admonition",{title:"Attention",type:"warning"},(0,i.yg)("p",{parentName:"admonition"},"By all means, ",(0,i.yg)("strong",{parentName:"p"},"never utilize the element's text / label in the naming of a test ID!"),"\nNamely, a test ID should never use ",(0,i.yg)("inlineCode",{parentName:"p"},"text")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"label")," props passed to a React Native component.")),(0,i.yg)("p",null,"There are at least 2 reasons why this is a very important rule:"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"Alternation of test ID's can lead to broken tests (test-ID based matchers become obsolete), and on-screen text can change frequently."),(0,i.yg)("li",{parentName:"ol"},"In apps supporting multiple languages, the on-screen text is likely to be different in each language. You want the same test code to be compatible with any language set into the test device, and you therefore need it have as little awareness to it as possible. Using test ID's is the best means to keep it that way.")),(0,i.yg)("h3",{id:"examples"},"Examples"),(0,i.yg)("p",null,"Based on the ",(0,i.yg)("inlineCode",{parentName:"p"},"ALL_CAPS")," convention, here is an example of a screen which test ID's illustrate the principles of this discussion:"),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"Test ID: Naming example",src:n(15102).A,width:"1083",height:"1105"})))}c.isMDXComponent=!0},15102:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/naming-example-42987c3697b6b78a3ae705fb15ea7a48.png"},86083:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/passTestID-11f72c3a0d97eff6a62c5591ae0ea1da.png"},43454:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/repetitiveComponentTestID-6fffa71a68b1f2015a4e8b96ece1e0e3.png"}}]); \ No newline at end of file diff --git a/assets/js/435645a6.e36a0578.js b/assets/js/435645a6.e36a0578.js deleted file mode 100644 index 9f0ce04a6f..0000000000 --- a/assets/js/435645a6.e36a0578.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2661],{15680:(e,t,n)=>{n.d(t,{xA:()=>m,yg:()=>g});var a=n(96540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function s(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):s(s({},t),e)),n},m=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,l=e.parentName,m=r(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,g=u["".concat(l,".").concat(d)]||u[d]||c[d]||o;return n?a.createElement(g,s(s({ref:t},m),{},{components:n})):a.createElement(g,s({ref:t},m))}));function g(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,s=new Array(o);s[0]=d;var r={};for(var l in t)hasOwnProperty.call(t,l)&&(r[l]=t[l]);r.originalType=e,r[u]="string"==typeof e?e:i,s[1]=r;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>s,default:()=>c,frontMatter:()=>o,metadata:()=>r,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));const o={},s="Adding test ID's to your components",r={unversionedId:"guide/test-id",id:"version-20.x/guide/test-id",title:"Adding test ID's to your components",description:"This guide was written primarily for React Native apps, but it can be generalized for testing any app, including native apps.",source:"@site/versioned_docs/version-20.x/guide/test-id.md",sourceDirName:"guide",slug:"/guide/test-id",permalink:"/Detox/docs/guide/test-id",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/guide/test-id.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Investigating Failures",permalink:"/Detox/docs/guide/investigating-test-failure"},next:{title:"Parallel Test Execution",permalink:"/Detox/docs/guide/parallel-test-execution"}},l={},p=[{value:"Pass testID to your native components",id:"pass-testid-to-your-native-components",level:2},{value:"Child elements",id:"child-elements",level:3},{value:"Repetitive components",id:"repetitive-components",level:3},{value:"Finding your test ID",id:"finding-your-test-id",level:2},{value:"Test ID naming - Best practices",id:"test-id-naming---best-practices",level:2},{value:"Use a consistent naming system",id:"use-a-consistent-naming-system",level:3},{value:"Use simple names",id:"use-simple-names",level:3},{value:"Dissociate test ID names",id:"dissociate-test-id-names",level:3},{value:"Examples",id:"examples",level:3}],m={toc:p},u="wrapper";function c(e){let{components:t,...o}=e;return(0,i.yg)(u,(0,a.A)({},m,o,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h1",{id:"adding-test-ids-to-your-components"},"Adding test ID's to your components"),(0,i.yg)("admonition",{title:"Note",type:"info"},(0,i.yg)("p",{parentName:"admonition"},"This guide was written primarily for React Native apps, but it can be generalized for testing any app, including native apps.")),(0,i.yg)("p",null,"While ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"view-element matching")," can be done in numerous ways, it is always the best idea to match based on something unique and decoupled, as it ensures that the test code is clear, stable and sustainable over time."),(0,i.yg)("p",null,"We recommend assigning unique test ID's to the elements you're aiming to interact with in your tests, and prefering matching based on those rather than on anything else. Test ID's are the least likely to change over time (compared with raw text, for example), and are locale-agnostic. Furthermore, utilizing unique test ID's across the app not only simplifies the identification and interaction with specific elements but also enhances code navigability, making it easier to locate elements when traversing the codebase."),(0,i.yg)("p",null,"In React Native applications, ",(0,i.yg)("inlineCode",{parentName:"p"},"View")," components have a dedicated ",(0,i.yg)("a",{parentName:"p",href:"https://reactnative.dev/docs/view#testid"},"test ID property")," that can be utilized:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx"},'\n \n Next\n \n\n')),(0,i.yg)("p",null,"For native apps, test ID's can be assigned by setting a value for the following properties:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"iOS:")," ",(0,i.yg)("a",{parentName:"li",href:"https://developer.apple.com/documentation/uikit/uiaccessibilityidentification/1623132-accessibilityidentifier"},(0,i.yg)("inlineCode",{parentName:"a"},"accessibilityIdentifier"))),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"Android:")," Default ",(0,i.yg)("a",{parentName:"li",href:"https://developer.android.com/reference/android/view/View#tags"},"viewTag"))),(0,i.yg)("h2",{id:"pass-testid-to-your-native-components"},"Pass testID to your native components"),(0,i.yg)("p",null,"Passing a ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," to your custom component props has no effect until you forward it down to a native component like ",(0,i.yg)("inlineCode",{parentName:"p"},"")," or ",(0,i.yg)("inlineCode",{parentName:"p"},""),"\nthat implements rendering it as an accessibility identifier in the native component hierarchy:"),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"Pass testID to native component",src:n(86083).A,width:"2116",height:"358"})),(0,i.yg)("p",null,"For example, you have ",(0,i.yg)("inlineCode",{parentName:"p"},"")," and you pass a ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," to it:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourScreen.jsx"',title:'"YourScreen.jsx"'},'function YourScreen() {\n return (\n \n );\n}\n')),(0,i.yg)("p",null,"Make sure that your implementation passes ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," to some React Native component that supports it:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourCustomComponent.jsx"',title:'"YourCustomComponent.jsx"'},"function YourCustomComponent(props) {\n return (\n// highlight-next-line\n \n Some text\n \n );\n}\n")),(0,i.yg)("h3",{id:"child-elements"},"Child elements"),(0,i.yg)("p",null,"If your component has several useful child elements, it is even a better idea to assign them some derived test IDs, e.g.:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourCustomComponent.jsx"',title:'"YourCustomComponent.jsx"'},"function YourCustomComponent(props) {\n return (\n// highlight-next-line\n \n Some text\n \n );\n}\n")),(0,i.yg)("p",null,"That way, you could refer to specific elements in Detox tests via the most basic and least ambiguous ",(0,i.yg)("inlineCode",{parentName:"p"},"by.id")," matchers, e.g.:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-js"},"expect(element(by.id('YourCustomComponent'))).toBeVisible();\nexpect(element(by.id('YourCustomComponent.label'))).toHaveText('Some text');\n")),(0,i.yg)("h3",{id:"repetitive-components"},"Repetitive components"),(0,i.yg)("p",null,"It is highly not recommended to use non-unique ",(0,i.yg)("inlineCode",{parentName:"p"},"testID"),", e.g. when your components get rendered in any sort of repeater or virtualized list:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx",metastring:'title="YourScreen.jsx"',title:'"YourScreen.jsx"'},"const ITEMS = [\n { title: 'First Item' },\n { title: 'Second Item' },\n { title: 'Third Item' },\n];\n\nfunction YourScreen() {\n const renderItem = ({ item }) => (\n// highlight-next-line\n \n );\n\n return (\n \n );\n}\n")),(0,i.yg)("p",null,"This would be a violation of accessibility guidelines and unnecessary complication for your test matchers.\nYou\u2019d also have to use extra matchers and ",(0,i.yg)("inlineCode",{parentName:"p"},".atIndex")," clarification:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-js"},"expect(element(by.id('listItem')).atIndex(2)).toHaveText('Third Item');\n")),(0,i.yg)("p",null,"Instead, you could generate a unique ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," for every list item with the ",(0,i.yg)("inlineCode",{parentName:"p"},"index")," property:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-jsx"}," const renderItem = ({ item, index }) => (\n \n );\n")),(0,i.yg)("p",null,"That way, your assertion would become simpler and more deterministic:"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-js"},"expect(element(by.id('listItem.3'))).toHaveText('Third Item');\n")),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"testID for repetitive components",src:n(43454).A,width:"2117",height:"624"})),(0,i.yg)("h2",{id:"finding-your-test-id"},"Finding your test ID"),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"Incorrect or absent ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," is a common cause for test failure.\nIf your test can't find your ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," and you can't see it either using tools described below, that usually means you haven't passed it down to this component.\nMake sure you keep forwarding it down until it reaches a native component.")),(0,i.yg)("p",null,"To make sure your ",(0,i.yg)("inlineCode",{parentName:"p"},"testID")," is indeed rendered in your app, you can use such tools as MacOS' built-in ",(0,i.yg)("a",{parentName:"p",href:"https://developer.apple.com/documentation/accessibility/inspecting-the-accessibility-of-screens"},"accessibility inspector")," for iOS, and ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-inspector"},"Detox Layout-inspector")," (setup required) for Android."),(0,i.yg)("h2",{id:"test-id-naming---best-practices"},"Test ID naming - Best practices"),(0,i.yg)("p",null,"Test ID's work best when they are unique, simple and concise. Here are our recommendations regarding what rules to follow in terms of naming."),(0,i.yg)("h3",{id:"use-a-consistent-naming-system"},"Use a consistent naming system"),(0,i.yg)("p",null,"Decide upon a system by which test ID's are named, and stick with it."),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"Use a consistent naming convention. An ",(0,i.yg)("inlineCode",{parentName:"p"},"ITEM_NAME_ALL_CAPS")," convention and an ",(0,i.yg)("inlineCode",{parentName:"p"},"ItemNameUpperCamelCase")," are both ok, but ",(0,i.yg)("strong",{parentName:"p"},"don't use them either intermittently nor in conjunction:")),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM_1")," - :white","_","check","_","mark:"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT")," & ",(0,i.yg)("inlineCode",{parentName:"li"},"SiteList_Item1")," - \u274c"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_Item1")," - \u274c"))),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"Consistently apply notations for special items. For example:"),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},"A ",(0,i.yg)("inlineCode",{parentName:"li"},"_ROOT")," postfix for screen-root or list-root items (e.g. ",(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT"),")"),(0,i.yg)("li",{parentName:"ul"},"A ",(0,i.yg)("inlineCode",{parentName:"li"},"_BTN")," for buttons / touchable CTA elements"))),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"Apply consistent prefixes as categories in order to introduce a top-level context to the test ID, distinguishing it from similar ones in various places in the app. The name of the associated screen can be useful in that sense. For example: ",(0,i.yg)("inlineCode",{parentName:"p"},"EDIT_PROFILE_SCREEN.DONE_BTN")," is better than just ",(0,i.yg)("inlineCode",{parentName:"p"},"DONE_BTN")," for a button that is inside a user profile editing screen. Also, things such as ",(0,i.yg)("inlineCode",{parentName:"p"},"NAV_TABS."),", ",(0,i.yg)("inlineCode",{parentName:"p"},"TOP_TABS.")," and ",(0,i.yg)("inlineCode",{parentName:"p"},"SIDE_MENU.")," can be used as good context providers.")),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"As explained in the section on passing test ID's to ",(0,i.yg)("em",{parentName:"p"},"child")," elements, drill down to the details of elements via a ",(0,i.yg)("em",{parentName:"p"},"chain of contexts"),'. Given the parent element-group of an element (for example, a card in a feed), use its own test ID as a prefix for the sub-items (e.g. an options "meatballs" / "kebab" CTA or an ',(0,i.yg)("em",{parentName:"p"},"edit")," button). For example:"),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1")," \u21d2",(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1.OPTIONS")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1.EDIT_BTN")),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ITEM1.TITLE")))))),(0,i.yg)("li",{parentName:"ol"},(0,i.yg)("p",{parentName:"li"},"In a large-scale, multi-module environment, apply a consistent module identifier as the module's test ID's prefix. For example:"),(0,i.yg)("ul",{parentName:"li"},(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"AUTH.LOGIN_SCREEN.EDIT_PASSWORD")," - the ",(0,i.yg)("inlineCode",{parentName:"li"},"AUTH.")," prefix suggests that were are under the context of a module handling Authentication matters.")))),(0,i.yg)("admonition",{type:"tip"},(0,i.yg)("p",{parentName:"admonition"},"Don't hesitate to articulate a well defined conventions manifest that all teams should adhere to.")),(0,i.yg)("h3",{id:"use-simple-names"},"Use simple names"),(0,i.yg)("p",null,"Stick to simple alpha-numeric characters, and simple separators. When it comes to test ID's, there's usually no reason to use special characters or emojis."),(0,i.yg)("p",null,"In addition, use test ID that clearly describe the associated element, but are also concise. For example:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST_ROOT")," - :white","_","check","_","mark:"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"MAIN_SITE_LIST_WRAPPER_ELEMENT")," - \u274c"),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("inlineCode",{parentName:"li"},"SITE_LIST@ITEM$1")," - \u274c")),(0,i.yg)("h3",{id:"dissociate-test-id-names"},"Dissociate test ID names"),(0,i.yg)("p",null,"Make sure the names you give test ID's are completely decoupled and dissociated from everything else in the system. In particular -"),(0,i.yg)("admonition",{title:"Attention",type:"warning"},(0,i.yg)("p",{parentName:"admonition"},"By all means, ",(0,i.yg)("strong",{parentName:"p"},"never utilize the element's text / label in the naming of a test ID!"),"\nNamely, a test ID should never use ",(0,i.yg)("inlineCode",{parentName:"p"},"text")," or ",(0,i.yg)("inlineCode",{parentName:"p"},"label")," props passed to a React Native component.")),(0,i.yg)("p",null,"There are at least 2 reasons why this is a very important rule:"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"Alternation of test ID's can lead to broken tests (test-ID based matchers become obsolete), and on-screen text can change frequently."),(0,i.yg)("li",{parentName:"ol"},"In apps supporting multiple languages, the on-screen text is likely to be different in each language. You want the same test code to be compatible with any language set into the test device, and you therefore need it have as little awareness to it as possible. Using test ID's is the best means to keep it that way.")),(0,i.yg)("h3",{id:"examples"},"Examples"),(0,i.yg)("p",null,"Based on the ",(0,i.yg)("inlineCode",{parentName:"p"},"ALL_CAPS")," convention, here is an example of a screen which test ID's illustrate the principles of this discussion:"),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"Test ID: Naming example",src:n(15102).A,width:"1083",height:"1105"})))}c.isMDXComponent=!0},15102:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/naming-example-42987c3697b6b78a3ae705fb15ea7a48.png"},86083:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/passTestID-11f72c3a0d97eff6a62c5591ae0ea1da.png"},43454:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/repetitiveComponentTestID-6fffa71a68b1f2015a4e8b96ece1e0e3.png"}}]); \ No newline at end of file diff --git a/assets/js/af67069a.de7dd6c3.js b/assets/js/af67069a.262c39d0.js similarity index 56% rename from assets/js/af67069a.de7dd6c3.js rename to assets/js/af67069a.262c39d0.js index bbfa851a4b..fe48abd132 100644 --- a/assets/js/af67069a.de7dd6c3.js +++ b/assets/js/af67069a.262c39d0.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2247],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),o=a(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,o.A)(r.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),o=a(96540),r=a(20053),i=a(23104),s=a(56347),l=a(57485),u=a(31682),p=a(89466);function c(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:o}}=e;return{value:t,label:a,attributes:n,default:o}}))}function d(e){const{values:t,children:a}=e;return(0,o.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),r=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,l.aZ)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(n.location.search);t.set(r,e),n.replace({...n.location,search:t.toString()})}),[r,n])]}function y(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,r=d(e),[i,s]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:r}))),[l,u]=g({queryString:a,groupId:n}),[c,y]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,r]=(0,p.Dv)(a);return[n,(0,o.useCallback)((e=>{a&&r.set(e)}),[a,r])]}({groupId:n}),h=(()=>{const e=l??c;return m({value:e,tabValues:r})?e:null})();(0,o.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,o.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),y(e)}),[u,y,r]),tabValues:r}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:l,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=u[a].value;n!==s&&(c(t),l(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:i}=e;return o.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const r=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===n));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function x(e){const t=y(e);return o.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},o.createElement(b,(0,n.A)({},e,t)),o.createElement(v,(0,n.A)({},e,t)))}function N(e){const t=(0,h.A)();return o.createElement(x,(0,n.A)({key:String(t)},e))}},21644:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>y,contentTitle:()=>m,default:()=>v,frontMatter:()=>d,metadata:()=>g,toc:()=>h});var n=a(58168),o=a(96540),r=a(15680),i=a(11470),s=a(19365),l=a(22355);const u={toc:[]},p="wrapper";function c(e){let{components:t,...a}=e;return(0,r.yg)(p,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)(o.Fragment,null,a.debug&&(0,r.yg)("div",null,(0,r.yg)("p",null,"Since this is a debug configuration, you need to have React Native packager running in parallel before you start\nDetox tests:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"npm start\n\n> react-native start\n\n# #######\n# ################\n# ######### #########\n# ######### ##########\n# ######### ###### #########\n# ##########################################\n# ##### ##################### #####\n# ##### ############## #####\n# ##### ### ###### ### #####\n# ##### ####### ####### #####\n# ##### ########### ########### #####\n# ##### ########################## #####\n# ##### ########################## #####\n# ##### ###################### ######\n# ###### ############# #######\n# ######### #### #########\n# ######### #########\n# ######### #########\n# #########\n#\n#\n# Welcome to Metro!\n# Fast - Scalable - Integrated\n")))),(0,r.yg)("p",null,"Now you can run your first test:"),(0,r.yg)(l.A,{language:"sh",mdxType:"CodeBlock"},"detox test --configuration ",a.configurationName))}c.isMDXComponent=!0;const d={},m="Your First Test",g={unversionedId:"introduction/your-first-test",id:"version-20.x/introduction/your-first-test",title:"Your First Test",description:"The previous articles have addressed the environment and project setup, and now it is time for writing",source:"@site/versioned_docs/version-20.x/introduction/your-first-test.mdx",sourceDirName:"introduction",slug:"/introduction/your-first-test",permalink:"/Detox/docs/introduction/your-first-test",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/introduction/your-first-test.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Project Setup",permalink:"/Detox/docs/introduction/project-setup"},next:{title:"How to Debug",permalink:"/Detox/docs/introduction/debugging"}},y={},h=[{value:"Writing a test",id:"writing-a-test",level:2},{value:"1. Create a test suite",id:"1-create-a-test-suite",level:3},{value:"2. Launch the application",id:"2-launch-the-application",level:3},{value:"3. Match an element",id:"3-match-an-element",level:3},{value:"4. Perform an action",id:"4-perform-an-action",level:3},{value:"5. Make an assertion",id:"5-make-an-assertion",level:3},{value:"Running tests",id:"running-tests",level:2}],f={toc:h},b="wrapper";function v(e){let{components:t,...a}=e;return(0,r.yg)(b,(0,n.A)({},f,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"your-first-test"},"Your First Test"),(0,r.yg)("p",null,"The previous articles have addressed the environment and project setup, and now it is time for writing\nand running the tests."),(0,r.yg)("p",null,"If you are eager to check first whether your build configuration was correct, you can skip writing a test for now and try ",(0,r.yg)("a",{parentName:"p",href:"#running-tests"},"running tests")," instead, to identify potential ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/troubleshooting/running-tests#no-simulators-found-ios"},"late issues")," caused by incorrect project configuration."),(0,r.yg)("h2",{id:"writing-a-test"},"Writing a test"),(0,r.yg)("p",null,"This subsection shows how to write a test which can:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("em",{parentName:"li"},"launch")," the application,"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("em",{parentName:"li"},"tap")," on a button,"),(0,r.yg)("li",{parentName:"ul"},"and ",(0,r.yg)("em",{parentName:"li"},"assert")," that some text appears as a result.")),(0,r.yg)("p",null,"Also, it will familiarize you with commonly used Detox ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions"},"actions"),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect"},"assertions")," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"matchers")," along the way."),(0,r.yg)("h3",{id:"1-create-a-test-suite"},"1. Create a test suite"),(0,r.yg)("admonition",{title:"Note",type:"info"},(0,r.yg)("p",{parentName:"admonition"},"You can also duplicate and modify a ",(0,r.yg)("inlineCode",{parentName:"p"},"e2e/starter.test.js")," file that was generated automatically by ",(0,r.yg)("inlineCode",{parentName:"p"},"detox init")," command.")),(0,r.yg)("p",null,"Create a new test file under your ",(0,r.yg)("inlineCode",{parentName:"p"},"e2e")," folder and add a similar test suite skeleton:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="e2e/yourTestName.test.js"',title:'"e2e/yourTestName.test.js"'},"describe('Example', () => {\n beforeAll(async () => {});\n\n beforeEach(async () => {});\n\n it('should test something', async () => {});\n});\n")),(0,r.yg)("h3",{id:"2-launch-the-application"},"2. Launch the application"),(0,r.yg)("p",null,"When your test starts, the application is not running yet. You need to call ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/device#devicelaunchappparams"},(0,r.yg)("inlineCode",{parentName:"a"},"device.launchApp()"))," at least once, e.g. in the ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAll")," hook:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"describe('Example', () => {\n beforeAll(async () => {\n await device.launchApp();\n });\n\n // \u2026\n});\n")),(0,r.yg)("p",null,"If your app supports deep links, you can configure it to ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/guide/mocking-open-with-url"},"start from a specific screen"),"."),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"It is a good idea to start every test from a fresh state, since the preceeding ones might leave your application\nin an unpredictable state if they fail."),(0,r.yg)("p",{parentName:"admonition"},"One way to do it is to launch the app as a new instance in ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeEach")," hook instead:"),(0,r.yg)("pre",{parentName:"admonition"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"beforeEach(async () => {\n await device.launchApp({ newInstance: true });\n});\n")),(0,r.yg)("p",{parentName:"admonition"},"The other way is to ",(0,r.yg)("em",{parentName:"p"},"reload React Native")," without restarting the app. Like any live reloading, it is apt to cause glitches for more complex apps,\nbut for simpler apps it proves to be a quicker way to reset the state between the tests:"),(0,r.yg)("pre",{parentName:"admonition"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"beforeEach(async () => {\n await device.reloadReactNative();\n});\n")),(0,r.yg)("p",{parentName:"admonition"},"So, pick your favorite one wisely, on the basis of ",(0,r.yg)("em",{parentName:"p"},"speed")," vs ",(0,r.yg)("em",{parentName:"p"},"stability")," considerations.")),(0,r.yg)("h3",{id:"3-match-an-element"},"3. Match an element"),(0,r.yg)("p",null,"The next step is to ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"match")," an element you want to interact with."),(0,r.yg)("p",null,"Detox provides many options to match an element ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#byidid"},(0,r.yg)("inlineCode",{parentName:"a"},"by.id()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#bylabellabel"},(0,r.yg)("inlineCode",{parentName:"a"},"by.label()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#bytexttext"},(0,r.yg)("inlineCode",{parentName:"a"},"by.text()"))," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"more"),".\nThe most common way to match elements is either by ",(0,r.yg)("em",{parentName:"p"},"id")," or ",(0,r.yg)("em",{parentName:"p"},"text"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"element(by.id('YourTestID')); // recommended\nelement(by.text('Element text'));\n")),(0,r.yg)("admonition",{title:"Best practice",type:"info"},(0,r.yg)("p",{parentName:"admonition"},"Try to use ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#byidid"},(0,r.yg)("inlineCode",{parentName:"a"},"by.id()"))," matcher wherever possible.\n",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/guide/test-id"},"Explore our guide")," to learn how to work with ",(0,r.yg)("inlineCode",{parentName:"p"},"testID")," props.")),(0,r.yg)("h3",{id:"4-perform-an-action"},"4. Perform an action"),(0,r.yg)("p",null,"Detox provides many actions on elements such as ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#tappoint"},(0,r.yg)("inlineCode",{parentName:"a"},"tap()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#swipedirection-speed-normalizedoffset-normalizedstartingpointx-normalizedstartingpointy"},(0,r.yg)("inlineCode",{parentName:"a"},"swipe()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#scrolloffset-direction-startpositionx-startpositiony"},(0,r.yg)("inlineCode",{parentName:"a"},"scroll()"))," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#methods"},"other interactions"),"."),(0,r.yg)("p",null,"Let's tap on an element for the sake of the example:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-jsx"},"describe('Example', () => {\n // \u2026\n\n it('should tap on a button', async () => {\n await element(by.id('ButtonID')).tap();\n });\n});\n")),(0,r.yg)("admonition",{title:"Note",type:"info"},(0,r.yg)("p",{parentName:"admonition"},'You don\'t need to wait or "sleep" after interacting with an element, because Detox already does it for you.\nIt synchronises with your application and waits after each action for all the foreground activities to finish before performing any further step.'),(0,r.yg)("p",{parentName:"admonition"},"While convenient, this feature goes sideways at times, for example, when your app has looping animations causing Detox to wait forever.\nThis is why you sometimes have to tweak your app specifically for Detox tests, and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/troubleshooting/synchronization"},"there is a special guide")," for that.")),(0,r.yg)("h3",{id:"5-make-an-assertion"},"5. Make an assertion"),(0,r.yg)("p",null,"An essential step of any test is ",(0,r.yg)("em",{parentName:"p"},"making an assertion"),"."),(0,r.yg)("p",null,"Detox provides its own ",(0,r.yg)("inlineCode",{parentName:"p"},"expect")," object, so that you can expect from any element ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#toexist"},(0,r.yg)("inlineCode",{parentName:"a"},"toExist()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tobevisible"},(0,r.yg)("inlineCode",{parentName:"a"},"toBeVisible()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tohavetexttext"},(0,r.yg)("inlineCode",{parentName:"a"},"toHaveText()"))," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#methods"},"more various things"),".\nAll the assertions can be inverted with ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#not"},(0,r.yg)("inlineCode",{parentName:"a"},"not")," property"),"."),(0,r.yg)("p",null,"Let's assert that our text is shown on a screen using ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tobevisible"},(0,r.yg)("inlineCode",{parentName:"a"},"toBeVisible()"))," function:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-jsx"},"describe('Example', () => {\n beforeAll(async () => {\n await device.launchApp();\n });\n\n beforeEach(async () => {\n await device.reloadReactNative();\n });\n\n it('should tap on button by id and expect some text to be visible', async () => {\n await element(by.id('ButtonID')).tap();\n await expect(element(by.text('The button has been pressed'))).toBeVisible();\n });\n});\n")),(0,r.yg)("p",null,"Note that instead of matching by text you can assert a specific text on an element with ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tohavetexttext"},(0,r.yg)("inlineCode",{parentName:"a"},"toHaveText()")),", e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-jsx"},"await expect(element(by.id('TextAfterButtonPressed'))).toHaveText('Button pressed');\n")),(0,r.yg)("h2",{id:"running-tests"},"Running tests"),(0,r.yg)(i.A,{groupId:"configurationName",mdxType:"Tabs"},(0,r.yg)(s.A,{value:"ios.sim.debug",label:"iOS (Debug)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"ios.sim.debug",debug:!0,mdxType:"RunningYourTest"})),(0,r.yg)(s.A,{value:"ios.sim.release",label:"iOS (Release)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"ios.sim.release",mdxType:"RunningYourTest"})),(0,r.yg)(s.A,{value:"android.emu.debug",label:"Android (Debug)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"android.emu.debug",debug:!0,mdxType:"RunningYourTest"})),(0,r.yg)(s.A,{value:"android.emu.release",label:"Android (Release)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"android.emu.release",mdxType:"RunningYourTest"}))),(0,r.yg)("p",null,"If you haven't changed the generated ",(0,r.yg)("inlineCode",{parentName:"p"},"e2e/starter.test.js"),", you are likely to see errors like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"}," FAIL e2e/starter.test.js (25.916 s)\n Example\n \u2715 should have welcome screen (662 ms)\n \u2715 should show hello screen after tap (236 ms)\n \u2715 should show world screen after tap (236 ms)\n\n \u25cf Example \u203a should have welcome screen\n\n Test Failed: No elements found for \u201cMATCHER(id == \u201cwelcome\u201d)\u201d\n\n HINT: To print view hierarchy on failed actions/matches, use log-level verbose or higher.\n\n 9 |\n 10 | it('should have welcome screen', async () => {\n > 11 | await expect(element(by.id('welcome'))).toBeVisible();\n | ^\n 12 | });\n 13 |\n 14 | it('should show hello screen after tap', async () => {\n\n at Object.toBeVisible (e2e/starter.test.js:11:45)\n\n \u2026\n")),(0,r.yg)("p",null,"If you have created your own test, and it is failing, examine the error message, check out our ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/guide/investigating-test-failure"},"Investigating Failures"),"\nand ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/introduction/debugging"},"Debugging")," guides, and run your tests again after you fix the issue."))}v.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2247],{19365:(e,t,a)=>{a.d(t,{A:()=>i});var n=a(96540),o=a(20053);const r={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:a,className:i}=e;return n.createElement("div",{role:"tabpanel",className:(0,o.A)(r.tabItem,i),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),o=a(96540),r=a(20053),i=a(23104),s=a(56347),l=a(57485),u=a(31682),p=a(89466);function c(e){return function(e){return o.Children.map(e,(e=>{if(!e||(0,o.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:a,attributes:n,default:o}}=e;return{value:t,label:a,attributes:n,default:o}}))}function d(e){const{values:t,children:a}=e;return(0,o.useMemo)((()=>{const e=t??c(a);return function(e){const t=(0,u.X)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,a])}function m(e){let{value:t,tabValues:a}=e;return a.some((e=>e.value===t))}function g(e){let{queryString:t=!1,groupId:a}=e;const n=(0,s.W6)(),r=function(e){let{queryString:t=!1,groupId:a}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!a)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return a??null}({queryString:t,groupId:a});return[(0,l.aZ)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(n.location.search);t.set(r,e),n.replace({...n.location,search:t.toString()})}),[r,n])]}function y(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,r=d(e),[i,s]=(0,o.useState)((()=>function(e){let{defaultValue:t,tabValues:a}=e;if(0===a.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:a}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${a.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=a.find((e=>e.default))??a[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:r}))),[l,u]=g({queryString:a,groupId:n}),[c,y]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,r]=(0,p.Dv)(a);return[n,(0,o.useCallback)((e=>{a&&r.set(e)}),[a,r])]}({groupId:n}),h=(()=>{const e=l??c;return m({value:e,tabValues:r})?e:null})();(0,o.useLayoutEffect)((()=>{h&&s(h)}),[h]);return{selectedValue:i,selectValue:(0,o.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);s(e),u(e),y(e)}),[u,y,r]),tabValues:r}}var h=a(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:s,selectValue:l,tabValues:u}=e;const p=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),d=e=>{const t=e.currentTarget,a=p.indexOf(t),n=u[a].value;n!==s&&(c(t),l(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=p.indexOf(e.currentTarget)+1;t=p[a]??p[0];break}case"ArrowLeft":{const a=p.indexOf(e.currentTarget)-1;t=p[a]??p[p.length-1];break}}t?.focus()};return o.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,r.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:i}=e;return o.createElement("li",(0,n.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>p.push(e),onKeyDown:m,onClick:d},i,{className:(0,r.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const r=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=r.find((e=>e.props.value===n));return e?(0,o.cloneElement)(e,{className:"margin-top--md"}):null}return o.createElement("div",{className:"margin-top--md"},r.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function x(e){const t=y(e);return o.createElement("div",{className:(0,r.A)("tabs-container",f.tabList)},o.createElement(b,(0,n.A)({},e,t)),o.createElement(v,(0,n.A)({},e,t)))}function N(e){const t=(0,h.A)();return o.createElement(x,(0,n.A)({key:String(t)},e))}},21644:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>y,contentTitle:()=>m,default:()=>v,frontMatter:()=>d,metadata:()=>g,toc:()=>h});var n=a(58168),o=a(96540),r=a(15680),i=a(11470),s=a(19365),l=a(22355);const u={toc:[]},p="wrapper";function c(e){let{components:t,...a}=e;return(0,r.yg)(p,(0,n.A)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)(o.Fragment,null,a.debug&&(0,r.yg)("div",null,(0,r.yg)("p",null,"Since this is a debug configuration, you need to have React Native packager running in parallel before you start\nDetox tests:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"npm start\n\n> react-native start\n\n# #######\n# ################\n# ######### #########\n# ######### ##########\n# ######### ###### #########\n# ##########################################\n# ##### ##################### #####\n# ##### ############## #####\n# ##### ### ###### ### #####\n# ##### ####### ####### #####\n# ##### ########### ########### #####\n# ##### ########################## #####\n# ##### ########################## #####\n# ##### ###################### ######\n# ###### ############# #######\n# ######### #### #########\n# ######### #########\n# ######### #########\n# #########\n#\n#\n# Welcome to Metro!\n# Fast - Scalable - Integrated\n")))),(0,r.yg)("p",null,"Now you can run your first test:"),(0,r.yg)(l.A,{language:"sh",mdxType:"CodeBlock"},"detox test --configuration ",a.configurationName))}c.isMDXComponent=!0;const d={},m="Your First Test",g={unversionedId:"introduction/your-first-test",id:"version-20.x/introduction/your-first-test",title:"Your First Test",description:"The previous articles have addressed the environment and project setup, and now it is time for writing",source:"@site/versioned_docs/version-20.x/introduction/your-first-test.mdx",sourceDirName:"introduction",slug:"/introduction/your-first-test",permalink:"/Detox/docs/introduction/your-first-test",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/introduction/your-first-test.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Project Setup",permalink:"/Detox/docs/introduction/project-setup"},next:{title:"How to Debug",permalink:"/Detox/docs/introduction/debugging"}},y={},h=[{value:"Writing a test",id:"writing-a-test",level:2},{value:"1. Create a test suite",id:"1-create-a-test-suite",level:3},{value:"2. Launch the application",id:"2-launch-the-application",level:3},{value:"3. Match an element",id:"3-match-an-element",level:3},{value:"4. Perform an action",id:"4-perform-an-action",level:3},{value:"5. Make an assertion",id:"5-make-an-assertion",level:3},{value:"Running tests",id:"running-tests",level:2}],f={toc:h},b="wrapper";function v(e){let{components:t,...a}=e;return(0,r.yg)(b,(0,n.A)({},f,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"your-first-test"},"Your First Test"),(0,r.yg)("p",null,"The previous articles have addressed the environment and project setup, and now it is time for writing\nand running the tests."),(0,r.yg)("p",null,"If you are eager to check first whether your build configuration was correct, you can skip writing a test for now and try ",(0,r.yg)("a",{parentName:"p",href:"#running-tests"},"running tests")," instead, to identify potential ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/troubleshooting/running-tests#no-simulators-found-ios"},"late issues")," caused by incorrect project configuration."),(0,r.yg)("h2",{id:"writing-a-test"},"Writing a test"),(0,r.yg)("p",null,"This subsection shows how to write a test which can:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("em",{parentName:"li"},"launch")," the application,"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("em",{parentName:"li"},"tap")," on a button,"),(0,r.yg)("li",{parentName:"ul"},"and ",(0,r.yg)("em",{parentName:"li"},"assert")," that some text appears as a result.")),(0,r.yg)("p",null,"Also, it will familiarize you with commonly used Detox ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions"},"actions"),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect"},"assertions")," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"matchers")," along the way."),(0,r.yg)("h3",{id:"1-create-a-test-suite"},"1. Create a test suite"),(0,r.yg)("admonition",{title:"Note",type:"info"},(0,r.yg)("p",{parentName:"admonition"},"You can also duplicate and modify a ",(0,r.yg)("inlineCode",{parentName:"p"},"e2e/starter.test.js")," file that was generated automatically by ",(0,r.yg)("inlineCode",{parentName:"p"},"detox init")," command.")),(0,r.yg)("p",null,"Create a new test file under your ",(0,r.yg)("inlineCode",{parentName:"p"},"e2e")," folder and add a similar test suite skeleton:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="e2e/yourTestName.test.js"',title:'"e2e/yourTestName.test.js"'},"describe('Example', () => {\n beforeAll(async () => {});\n\n beforeEach(async () => {});\n\n it('should test something', async () => {});\n});\n")),(0,r.yg)("h3",{id:"2-launch-the-application"},"2. Launch the application"),(0,r.yg)("p",null,"When your test starts, the application is not running yet. You need to call ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/device#devicelaunchappparams"},(0,r.yg)("inlineCode",{parentName:"a"},"device.launchApp()"))," at least once, e.g. in the ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAll")," hook:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"describe('Example', () => {\n beforeAll(async () => {\n await device.launchApp();\n });\n\n // \u2026\n});\n")),(0,r.yg)("p",null,"If your app supports deep links, you can configure it to ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/guide/mocking-open-with-url"},"start from a specific screen"),"."),(0,r.yg)("admonition",{type:"tip"},(0,r.yg)("p",{parentName:"admonition"},"It is a good idea to start every test from a fresh state, since the preceding ones might leave your application\nin an unpredictable state if they fail."),(0,r.yg)("p",{parentName:"admonition"},"One way to do it is to launch the app as a new instance in ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeEach")," hook instead:"),(0,r.yg)("pre",{parentName:"admonition"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"beforeEach(async () => {\n await device.launchApp({ newInstance: true });\n});\n")),(0,r.yg)("p",{parentName:"admonition"},"The other way is to ",(0,r.yg)("em",{parentName:"p"},"reload React Native")," without restarting the app. Like any live reloading, it is apt to cause glitches for more complex apps,\nbut for simpler apps it proves to be a quicker way to reset the state between the tests:"),(0,r.yg)("pre",{parentName:"admonition"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"beforeEach(async () => {\n await device.reloadReactNative();\n});\n")),(0,r.yg)("p",{parentName:"admonition"},"So, pick your favorite one wisely, on the basis of ",(0,r.yg)("em",{parentName:"p"},"speed")," vs ",(0,r.yg)("em",{parentName:"p"},"stability")," considerations.")),(0,r.yg)("h3",{id:"3-match-an-element"},"3. Match an element"),(0,r.yg)("p",null,"The next step is to ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"match")," an element you want to interact with."),(0,r.yg)("p",null,"Detox provides many options to match an element ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#byidid"},(0,r.yg)("inlineCode",{parentName:"a"},"by.id()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#bylabellabel"},(0,r.yg)("inlineCode",{parentName:"a"},"by.label()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#bytexttext"},(0,r.yg)("inlineCode",{parentName:"a"},"by.text()"))," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},"more"),".\nThe most common way to match elements is either by ",(0,r.yg)("em",{parentName:"p"},"id")," or ",(0,r.yg)("em",{parentName:"p"},"text"),":"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"element(by.id('YourTestID')); // recommended\nelement(by.text('Element text'));\n")),(0,r.yg)("admonition",{title:"Best practice",type:"info"},(0,r.yg)("p",{parentName:"admonition"},"Try to use ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers#byidid"},(0,r.yg)("inlineCode",{parentName:"a"},"by.id()"))," matcher wherever possible.\n",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/guide/test-id"},"Explore our guide")," to learn how to work with ",(0,r.yg)("inlineCode",{parentName:"p"},"testID")," props.")),(0,r.yg)("h3",{id:"4-perform-an-action"},"4. Perform an action"),(0,r.yg)("p",null,"Detox provides many actions on elements such as ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#tappoint"},(0,r.yg)("inlineCode",{parentName:"a"},"tap()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#swipedirection-speed-normalizedoffset-normalizedstartingpointx-normalizedstartingpointy"},(0,r.yg)("inlineCode",{parentName:"a"},"swipe()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#scrolloffset-direction-startpositionx-startpositiony"},(0,r.yg)("inlineCode",{parentName:"a"},"scroll()"))," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions#methods"},"other interactions"),"."),(0,r.yg)("p",null,"Let's tap on an element for the sake of the example:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-jsx"},"describe('Example', () => {\n // \u2026\n\n it('should tap on a button', async () => {\n await element(by.id('ButtonID')).tap();\n });\n});\n")),(0,r.yg)("admonition",{title:"Note",type:"info"},(0,r.yg)("p",{parentName:"admonition"},'You don\'t need to wait or "sleep" after interacting with an element, because Detox already does it for you.\nIt synchronises with your application and waits after each action for all the foreground activities to finish before performing any further step.'),(0,r.yg)("p",{parentName:"admonition"},"While convenient, this feature goes sideways at times, for example, when your app has looping animations causing Detox to wait forever.\nThis is why you sometimes have to tweak your app specifically for Detox tests, and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/troubleshooting/synchronization"},"there is a special guide")," for that.")),(0,r.yg)("h3",{id:"5-make-an-assertion"},"5. Make an assertion"),(0,r.yg)("p",null,"An essential step of any test is ",(0,r.yg)("em",{parentName:"p"},"making an assertion"),"."),(0,r.yg)("p",null,"Detox provides its own ",(0,r.yg)("inlineCode",{parentName:"p"},"expect")," object, so that you can expect from any element ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#toexist"},(0,r.yg)("inlineCode",{parentName:"a"},"toExist()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tobevisible"},(0,r.yg)("inlineCode",{parentName:"a"},"toBeVisible()")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tohavetexttext"},(0,r.yg)("inlineCode",{parentName:"a"},"toHaveText()"))," and ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#methods"},"more various things"),".\nAll the assertions can be inverted with ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#not"},(0,r.yg)("inlineCode",{parentName:"a"},"not")," property"),"."),(0,r.yg)("p",null,"Let's assert that our text is shown on a screen using ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tobevisible"},(0,r.yg)("inlineCode",{parentName:"a"},"toBeVisible()"))," function:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-jsx"},"describe('Example', () => {\n beforeAll(async () => {\n await device.launchApp();\n });\n\n beforeEach(async () => {\n await device.reloadReactNative();\n });\n\n it('should tap on button by id and expect some text to be visible', async () => {\n await element(by.id('ButtonID')).tap();\n await expect(element(by.text('The button has been pressed'))).toBeVisible();\n });\n});\n")),(0,r.yg)("p",null,"Note that instead of matching by text you can assert a specific text on an element with ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect#tohavetexttext"},(0,r.yg)("inlineCode",{parentName:"a"},"toHaveText()")),", e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-jsx"},"await expect(element(by.id('TextAfterButtonPressed'))).toHaveText('Button pressed');\n")),(0,r.yg)("h2",{id:"running-tests"},"Running tests"),(0,r.yg)(i.A,{groupId:"configurationName",mdxType:"Tabs"},(0,r.yg)(s.A,{value:"ios.sim.debug",label:"iOS (Debug)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"ios.sim.debug",debug:!0,mdxType:"RunningYourTest"})),(0,r.yg)(s.A,{value:"ios.sim.release",label:"iOS (Release)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"ios.sim.release",mdxType:"RunningYourTest"})),(0,r.yg)(s.A,{value:"android.emu.debug",label:"Android (Debug)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"android.emu.debug",debug:!0,mdxType:"RunningYourTest"})),(0,r.yg)(s.A,{value:"android.emu.release",label:"Android (Release)",mdxType:"TabItem"},(0,r.yg)(c,{configurationName:"android.emu.release",mdxType:"RunningYourTest"}))),(0,r.yg)("p",null,"If you haven't changed the generated ",(0,r.yg)("inlineCode",{parentName:"p"},"e2e/starter.test.js"),", you are likely to see errors like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre"}," FAIL e2e/starter.test.js (25.916 s)\n Example\n \u2715 should have welcome screen (662 ms)\n \u2715 should show hello screen after tap (236 ms)\n \u2715 should show world screen after tap (236 ms)\n\n \u25cf Example \u203a should have welcome screen\n\n Test Failed: No elements found for \u201cMATCHER(id == \u201cwelcome\u201d)\u201d\n\n HINT: To print view hierarchy on failed actions/matches, use log-level verbose or higher.\n\n 9 |\n 10 | it('should have welcome screen', async () => {\n > 11 | await expect(element(by.id('welcome'))).toBeVisible();\n | ^\n 12 | });\n 13 |\n 14 | it('should show hello screen after tap', async () => {\n\n at Object.toBeVisible (e2e/starter.test.js:11:45)\n\n \u2026\n")),(0,r.yg)("p",null,"If you have created your own test, and it is failing, examine the error message, check out our ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/guide/investigating-test-failure"},"Investigating Failures"),"\nand ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/introduction/debugging"},"Debugging")," guides, and run your tests again after you fix the issue."))}v.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f431fa1f.c9fbdd0f.js b/assets/js/f431fa1f.c359d024.js similarity index 96% rename from assets/js/f431fa1f.c9fbdd0f.js rename to assets/js/f431fa1f.c359d024.js index 3eac46d94c..ba25c84c49 100644 --- a/assets/js/f431fa1f.c9fbdd0f.js +++ b/assets/js/f431fa1f.c359d024.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[1707],{15680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>m});var a=n(96540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,m=u["".concat(s,".").concat(d)]||u[d]||g[d]||r;return n?a.createElement(m,o(o({ref:t},c),{},{components:n})):a.createElement(m,o({ref:t},c))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));const r={},o="Using Launch Arguments",l={unversionedId:"guide/launch-args",id:"version-20.x/guide/launch-args",title:"Using Launch Arguments",description:"In Detox, the app under test is launched via an explicit call to device.launchApp(). Through various means, Detox enables specifying a set of user-defined arguments (key-value pairs) to be passed on to the app when launched, so as to make them available inside the launched app itself at runtime (both on the native side, and - if applicable, on the JavaScript side).",source:"@site/versioned_docs/version-20.x/guide/launch-args.md",sourceDirName:"guide",slug:"/guide/launch-args",permalink:"/Detox/docs/guide/launch-args",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/guide/launch-args.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Mocking",permalink:"/Detox/docs/guide/mocking"},next:{title:"Mocking Open With URL (Deep Links)",permalink:"/Detox/docs/guide/mocking-open-with-url"}},s={},p=[{value:"Motivation",id:"motivation",level:3},{value:"Arguments Setup",id:"arguments-setup",level:3},{value:"In-App Arguments Access",id:"in-app-arguments-access",level:3}],c={toc:p},u="wrapper";function g(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h1",{id:"using-launch-arguments"},"Using Launch Arguments"),(0,i.yg)("p",null,"In Detox, the app under test is launched via an explicit call to ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/api/device"},(0,i.yg)("inlineCode",{parentName:"a"},"device.launchApp()")),". Through various means, Detox enables specifying a set of user-defined arguments (key-value pairs) to be passed on to the app when launched, so as to make them available inside the launched app itself at runtime (both on the native side, and - if applicable, on the JavaScript side)."),(0,i.yg)("h3",{id:"motivation"},"Motivation"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"If this is clear to you first hand, you can skip right to the section about arguments setup.")),(0,i.yg)("p",null,"In particular, the common use case of using launch argument (although not distinctly), is for ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/guide/mocking"},"mocking")," external entities such as servers - replacing them with equivalent ",(0,i.yg)("em",{parentName:"p"},"mock servers"),", sporting equivalent (yet fake) API-endpoints that run alongside the testing host (i.e. the one running Detox). These mock servers can typically be configured during the test, to return deterministic responses to network requests coming from the app."),(0,i.yg)("p",null,"Typically, the process of setting up such servers - especially in a parallel test-execution environment, involves three major steps (within the context of a test set-up):"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"Allocating a port for a mock server, dynamically."),(0,i.yg)("li",{parentName:"ol"},"Bringing up a mock server instance bound to that port (e.g. at ",(0,i.yg)("inlineCode",{parentName:"li"},"localhost:1234"),")."),(0,i.yg)("li",{parentName:"ol"},"Launching the app with a predefined argument that holds the port, for example ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort=1234"),".\n(It is assumed here that there\u2019s designated mocked code inside the app that can read ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort")," and rewire all connections to ",(0,i.yg)("inlineCode",{parentName:"li"},"localhost:1234")," instead of to the real production server).")),(0,i.yg)("p",null,"In this context, launch argument are useful for implementing step ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3"},"#3"),"."),(0,i.yg)("h3",{id:"arguments-setup"},"Arguments Setup"),(0,i.yg)("p",null,"User-defined launch arguments specification is very flexible, and can be defined on 4 levels:"),(0,i.yg)("table",null,(0,i.yg)("thead",{parentName:"table"},(0,i.yg)("tr",{parentName:"thead"},(0,i.yg)("th",{parentName:"tr",align:null},"Level"),(0,i.yg)("th",{parentName:"tr",align:null},"Description"))),(0,i.yg)("tbody",{parentName:"table"},(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"1. Static Configuration"),(0,i.yg)("td",{parentName:"tr",align:null},"As a part of a static ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/config/overview"},"Detox configuration"),", using the ",(0,i.yg)("inlineCode",{parentName:"td"},"launchArgs")," property.",(0,i.yg)("br",null),"This is can sufficient, for example, if you only require one mock server instance, and can use the same static port throughout the entire testing execution session.")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"2. Static via CLI"),(0,i.yg)("td",{parentName:"tr",align:null},"As arguments specified explicitly in the ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/cli/test"},"command-line")," execution of ",(0,i.yg)("inlineCode",{parentName:"td"},"detox test"),", using ",(0,i.yg)("inlineCode",{parentName:"td"},"--app-launch-args"),".")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"3.",(0,i.yg)("inlineCode",{parentName:"td"},"device.appLaunchArgs")),(0,i.yg)("td",{parentName:"tr",align:null},"Dynamically, using the ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/api/device#deviceapplaunchargs"},(0,i.yg)("inlineCode",{parentName:"a"},"device.appLaunchArgs"))," API, which initially holds the static configuration, and then allows for the modification of it before applied through ",(0,i.yg)("inlineCode",{parentName:"td"},"device.launchApp()"),".",(0,i.yg)("br",null),"Mostly required in complex test environments, where the servers and ports are dynamic, and are determined via distinct software components (e.g. separate test kits).")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"4. ",(0,i.yg)("inlineCode",{parentName:"td"},"device.launchApp()")," with ",(0,i.yg)("inlineCode",{parentName:"td"},"launchArgs")),(0,i.yg)("td",{parentName:"tr",align:null},"Dynamically and explicitly, using on-site arguments specified in calls to ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/api/device#devicelaunchappparams"},(0,i.yg)("inlineCode",{parentName:"a"},"device.launchApp()"))," through the ",(0,i.yg)("inlineCode",{parentName:"td"},"launchArgs")," parameter.",(0,i.yg)("br",null),"Ideal for fairly simple test environments, where the ports are dynamic but are in complete control of the user.")))),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"Important: Arguments specified in each level take precedence over equivalent underlying levels"),"."),(0,i.yg)("p",null,"Examples:"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"In an environment where ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort")," is statically pre-set to ",(0,i.yg)("inlineCode",{parentName:"li"},"1001")," in Detox configuration, and then set to ",(0,i.yg)("inlineCode",{parentName:"li"},"1003")," using ",(0,i.yg)("inlineCode",{parentName:"li"},"device.appLaunchArgs")," inside a test, the app would eventually be launched with ",(0,i.yg)("inlineCode",{parentName:"li"},"1003")," as its value, in calls to ",(0,i.yg)("inlineCode",{parentName:"li"},"device.launchApp()")," in that test."),(0,i.yg)("li",{parentName:"ol"},"(Scenario continues) In subsequent calls to ",(0,i.yg)("inlineCode",{parentName:"li"},"device.launchApp()")," with this parameter: ",(0,i.yg)("inlineCode",{parentName:"li"},"device.launchApp({ launchArgs: {mockServerPort: 1004} })"),", the app will be (re-)launched with ",(0,i.yg)("inlineCode",{parentName:"li"},"1004")," as the value for ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort"),".")),(0,i.yg)("h3",{id:"in-app-arguments-access"},"In-App Arguments Access"),(0,i.yg)("p",null,"Our official recommendation for getting the arguments inside the app is by integrating the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/iamolegga/react-native-launch-arguments"},"react-native-launch-arguments")," project, which provides that seamlessly. For those who are interested, here are the underlying details:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"On iOS, the specified launch arguments are passed as the process launch arguments and available through normal means, such as accesing ",(0,i.yg)("inlineCode",{parentName:"li"},"[[NSProcessInfo processInfo] arguments]"),"."),(0,i.yg)("li",{parentName:"ul"},"On Android, the launch arguments are set as bundle-extra\u2019s into the activity\u2019s intent. They are therefore accessible on the native side via the current activity as: ",(0,i.yg)("inlineCode",{parentName:"li"},'currentActivity.getIntent().getBundleExtra("launchArgs")'),".")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[1707],{15680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>m});var a=n(96540);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,m=u["".concat(s,".").concat(d)]||u[d]||g[d]||r;return n?a.createElement(m,o(o({ref:t},c),{},{components:n})):a.createElement(m,o({ref:t},c))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>g,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=n(58168),i=(n(96540),n(15680));const r={},o="Using Launch Arguments",l={unversionedId:"guide/launch-args",id:"version-20.x/guide/launch-args",title:"Using Launch Arguments",description:"In Detox, the app under test is launched via an explicit call to device.launchApp(). Through various means, Detox enables specifying a set of user-defined arguments (key-value pairs) to be passed on to the app when launched, so as to make them available inside the launched app itself at runtime (both on the native side, and - if applicable, on the JavaScript side).",source:"@site/versioned_docs/version-20.x/guide/launch-args.md",sourceDirName:"guide",slug:"/guide/launch-args",permalink:"/Detox/docs/guide/launch-args",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/guide/launch-args.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Mocking",permalink:"/Detox/docs/guide/mocking"},next:{title:"Mocking Open With URL (Deep Links)",permalink:"/Detox/docs/guide/mocking-open-with-url"}},s={},p=[{value:"Motivation",id:"motivation",level:3},{value:"Arguments Setup",id:"arguments-setup",level:3},{value:"In-App Arguments Access",id:"in-app-arguments-access",level:3}],c={toc:p},u="wrapper";function g(e){let{components:t,...n}=e;return(0,i.yg)(u,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h1",{id:"using-launch-arguments"},"Using Launch Arguments"),(0,i.yg)("p",null,"In Detox, the app under test is launched via an explicit call to ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/api/device"},(0,i.yg)("inlineCode",{parentName:"a"},"device.launchApp()")),". Through various means, Detox enables specifying a set of user-defined arguments (key-value pairs) to be passed on to the app when launched, so as to make them available inside the launched app itself at runtime (both on the native side, and - if applicable, on the JavaScript side)."),(0,i.yg)("h3",{id:"motivation"},"Motivation"),(0,i.yg)("blockquote",null,(0,i.yg)("p",{parentName:"blockquote"},"If this is clear to you first hand, you can skip right to the section about arguments setup.")),(0,i.yg)("p",null,"In particular, the common use case of using launch argument (although not distinctly), is for ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/guide/mocking"},"mocking")," external entities such as servers - replacing them with equivalent ",(0,i.yg)("em",{parentName:"p"},"mock servers"),", sporting equivalent (yet fake) API-endpoints that run alongside the testing host (i.e. the one running Detox). These mock servers can typically be configured during the test, to return deterministic responses to network requests coming from the app."),(0,i.yg)("p",null,"Typically, the process of setting up such servers - especially in a parallel test-execution environment, involves three major steps (within the context of a test set-up):"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"Allocating a port for a mock server, dynamically."),(0,i.yg)("li",{parentName:"ol"},"Bringing up a mock server instance bound to that port (e.g. at ",(0,i.yg)("inlineCode",{parentName:"li"},"localhost:1234"),")."),(0,i.yg)("li",{parentName:"ol"},"Launching the app with a predefined argument that holds the port, for example ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort=1234"),".\n(It is assumed here that there\u2019s designated mocked code inside the app that can read ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort")," and rewire all connections to ",(0,i.yg)("inlineCode",{parentName:"li"},"localhost:1234")," instead of to the real production server).")),(0,i.yg)("p",null,"In this context, launch argument are useful for implementing step ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3"},"#3"),"."),(0,i.yg)("h3",{id:"arguments-setup"},"Arguments Setup"),(0,i.yg)("p",null,"User-defined launch arguments specification is very flexible, and can be defined on 4 levels:"),(0,i.yg)("table",null,(0,i.yg)("thead",{parentName:"table"},(0,i.yg)("tr",{parentName:"thead"},(0,i.yg)("th",{parentName:"tr",align:null},"Level"),(0,i.yg)("th",{parentName:"tr",align:null},"Description"))),(0,i.yg)("tbody",{parentName:"table"},(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"1. Static Configuration"),(0,i.yg)("td",{parentName:"tr",align:null},"As a part of a static ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/config/overview"},"Detox configuration"),", using the ",(0,i.yg)("inlineCode",{parentName:"td"},"launchArgs")," property.",(0,i.yg)("br",null),"This is can sufficient, for example, if you only require one mock server instance, and can use the same static port throughout the entire testing execution session.")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"2. Static via CLI"),(0,i.yg)("td",{parentName:"tr",align:null},"As arguments specified explicitly in the ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/cli/test"},"command-line")," execution of ",(0,i.yg)("inlineCode",{parentName:"td"},"detox test"),", using ",(0,i.yg)("inlineCode",{parentName:"td"},"--app-launch-args"),".")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"3.",(0,i.yg)("inlineCode",{parentName:"td"},"device.appLaunchArgs")),(0,i.yg)("td",{parentName:"tr",align:null},"Dynamically, using the ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/api/device#deviceapplaunchargs"},(0,i.yg)("inlineCode",{parentName:"a"},"device.appLaunchArgs"))," API, which initially holds the static configuration, and then allows for the modification of it before applied through ",(0,i.yg)("inlineCode",{parentName:"td"},"device.launchApp()"),".",(0,i.yg)("br",null),"Mostly required in complex test environments, where the servers and ports are dynamic, and are determined via distinct software components (e.g. separate test kits).")),(0,i.yg)("tr",{parentName:"tbody"},(0,i.yg)("td",{parentName:"tr",align:null},"4. ",(0,i.yg)("inlineCode",{parentName:"td"},"device.launchApp()")," with ",(0,i.yg)("inlineCode",{parentName:"td"},"launchArgs")),(0,i.yg)("td",{parentName:"tr",align:null},"Dynamically and explicitly, using on-site arguments specified in calls to ",(0,i.yg)("a",{parentName:"td",href:"/Detox/docs/api/device#devicelaunchappparams"},(0,i.yg)("inlineCode",{parentName:"a"},"device.launchApp()"))," through the ",(0,i.yg)("inlineCode",{parentName:"td"},"launchArgs")," parameter.",(0,i.yg)("br",null),"Ideal for fairly simple test environments, where the ports are dynamic but are in complete control of the user.")))),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"Important: Arguments specified in each level take precedence over equivalent underlying levels"),"."),(0,i.yg)("p",null,"Examples:"),(0,i.yg)("ol",null,(0,i.yg)("li",{parentName:"ol"},"In an environment where ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort")," is statically pre-set to ",(0,i.yg)("inlineCode",{parentName:"li"},"1001")," in Detox configuration, and then set to ",(0,i.yg)("inlineCode",{parentName:"li"},"1003")," using ",(0,i.yg)("inlineCode",{parentName:"li"},"device.appLaunchArgs")," inside a test, the app would eventually be launched with ",(0,i.yg)("inlineCode",{parentName:"li"},"1003")," as its value, in calls to ",(0,i.yg)("inlineCode",{parentName:"li"},"device.launchApp()")," in that test."),(0,i.yg)("li",{parentName:"ol"},"(Scenario continues) In subsequent calls to ",(0,i.yg)("inlineCode",{parentName:"li"},"device.launchApp()")," with this parameter: ",(0,i.yg)("inlineCode",{parentName:"li"},"device.launchApp({ launchArgs: {mockServerPort: 1004} })"),", the app will be (re-)launched with ",(0,i.yg)("inlineCode",{parentName:"li"},"1004")," as the value for ",(0,i.yg)("inlineCode",{parentName:"li"},"mockServerPort"),".")),(0,i.yg)("h3",{id:"in-app-arguments-access"},"In-App Arguments Access"),(0,i.yg)("p",null,"Our official recommendation for getting the arguments inside the app is by integrating the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/iamolegga/react-native-launch-arguments"},"react-native-launch-arguments")," project, which provides that seamlessly. For those who are interested, here are the underlying details:"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},"On iOS, the specified launch arguments are passed as the process launch arguments and available through normal means, such as accessing ",(0,i.yg)("inlineCode",{parentName:"li"},"[[NSProcessInfo processInfo] arguments]"),"."),(0,i.yg)("li",{parentName:"ul"},"On Android, the launch arguments are set as bundle-extra\u2019s into the activity\u2019s intent. They are therefore accessible on the native side via the current activity as: ",(0,i.yg)("inlineCode",{parentName:"li"},'currentActivity.getIntent().getBundleExtra("launchArgs")'),".")))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.402ea803.js b/assets/js/runtime~main.413e1946.js similarity index 86% rename from assets/js/runtime~main.402ea803.js rename to assets/js/runtime~main.413e1946.js index e5275d68e9..8a8fd6b836 100644 --- a/assets/js/runtime~main.402ea803.js +++ b/assets/js/runtime~main.413e1946.js @@ -1 +1 @@ -(()=>{"use strict";var e,f,a,d,b,c={},t={};function r(e){var f=t[e];if(void 0!==f)return f.exports;var a=t[e]={id:e,loaded:!1,exports:{}};return c[e].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}r.m=c,e=[],r.O=(f,a,d,b)=>{if(!a){var c=1/0;for(i=0;i=b)&&Object.keys(r.O).every((e=>r.O[e](a[o])))?a.splice(o--,1):(t=!1,b0&&e[i-1][2]>b;i--)e[i]=e[i-1];e[i]=[a,d,b]},r.n=e=>{var f=e&&e.__esModule?()=>e.default:()=>e;return r.d(f,{a:f}),f},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var b=Object.create(null);r.r(b);var c={};f=f||[null,a({}),a([]),a(a)];for(var t=2&d&&e;"object"==typeof t&&!~f.indexOf(t);t=a(t))Object.getOwnPropertyNames(t).forEach((f=>c[f]=()=>e[f]));return c.default=()=>e,r.d(b,c),b},r.d=(e,f)=>{for(var a in f)r.o(f,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:f[a]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((f,a)=>(r.f[a](e,f),f)),[])),r.u=e=>"assets/js/"+({2:"18fc5556",53:"fe23c957",68:"268dddcf",96:"121cced5",99:"0a2b2829",123:"3bc305d8",178:"861bdbb6",187:"45e366b7",235:"85c7de33",289:"9a6ca8b8",298:"a4761f6f",323:"cb38042d",332:"e6cdcb35",347:"6fb4057b",456:"e9d07a0a",577:"d9894068",612:"b1c3f2c7",641:"86a9c212",643:"20110cf9",656:"d8b689b0",682:"1373a77b",708:"9509e94f",770:"df0b489b",1079:"1c545b73",1138:"96997528",1211:"26368098",1218:"5bc90040",1274:"f02f7df4",1513:"8ab53c77",1533:"9a34b858",1539:"3fb5a56d",1581:"328083ea",1700:"fc9f0a8f",1707:"f431fa1f",1839:"66491fb6",1874:"5fc994c2",1987:"ecfe08ed",1998:"5133b137",2043:"4e8770ad",2068:"5d002dae",2083:"3d4c33f6",2120:"2dcc617a",2124:"0d1d5dba",2138:"1a4e3797",2141:"933bed1e",2154:"9017a355",2247:"af67069a",2269:"3f859f05",2270:"c62bfd53",2338:"7b8d824d",2373:"7a99341f",2377:"38bf2aac",2419:"9eb50243",2438:"4afa4b8a",2459:"649c4fa3",2460:"37fdb427",2496:"85addd09",2525:"d350cff3",2581:"dc701447",2634:"c4f5d8e4",2661:"435645a6",2698:"373c35af",2707:"c50db514",2711:"9e4087bc",2905:"ddab1e1f",2920:"398b3246",2965:"3e7ee0fc",3010:"a55aca30",3015:"0e97a833",3022:"e6bddadc",3103:"f2f4b8a7",3119:"f14c3b1d",3134:"e432d2f9",3150:"090a441b",3182:"6e1398c4",3206:"74a579df",3249:"ccc49370",3360:"c12557ec",3372:"b49de2ad",3379:"f97fefc4",3392:"0ef19a76",3414:"04d1378b",3421:"bab8a798",3486:"cae448e5",3559:"4898f926",3671:"3f3efe2b",3818:"7358fe14",3833:"c262e01e",3890:"06556991",3892:"24602229",3894:"120e2fab",3995:"beec6c9a",4005:"155ec335",4034:"fa42474c",4078:"e8bfc54f",4099:"bc243f9c",4122:"c8447d5b",4196:"f9078c13",4203:"9338ecde",4216:"50214cd4",4261:"66c89031",4324:"9b67fd78",4368:"ef7da448",4480:"4d2064e8",4511:"a6860cb2",4557:"c74a7097",4651:"040764b7",4778:"a622d695",4813:"6875c492",4871:"85a74db3",4879:"00424e3f",4930:"3f50474f",5104:"2ec4e639",5110:"ddb15f76",5210:"4a184f52",5235:"b48043f7",5333:"743699a6",5351:"df5bc064",5403:"684e0aea",5458:"5125c427",5465:"d389a7b2",5484:"30a967f3",5564:"5fcf77d7",5599:"3abc4359",5763:"851303a1",5844:"329947da",5928:"ae694851",5992:"3c59129e",6002:"6781c826",6044:"cd796466",6101:"750255a9",6104:"8e7c33d6",6291:"1ee22d16",6388:"c7632c1f",6417:"79ac7843",6461:"c83bcda6",6527:"32a9b7bf",6618:"a8a1de49",6643:"289d965c",6646:"e453af6e",6769:"1a01fdf1",6887:"19ee85cd",6913:"41e12717",6938:"b80a1ac9",6941:"f98b7248",6990:"8f43d633",7024:"0f6ee85b",7056:"40113ee6",7109:"54c48e38",7126:"5e26652b",7153:"6af18994",7243:"5a9cd6fb",7308:"48c4a2a0",7321:"23a61782",7360:"af952e90",7367:"ea7b1b31",7472:"814f3328",7473:"7accbb75",7529:"90ca9965",7577:"5b12c1a9",7583:"af558054",7606:"9f23071e",7608:"2d84255a",7619:"478bcf42",7643:"a6aa9e1f",7696:"3974811d",7714:"170ab94c",7715:"0b72a6ff",7742:"dcfd3b61",7746:"7dcc0419",7761:"ba1e31c2",7813:"656a21b7",7839:"7d2f6bd9",7846:"dd5377a3",7886:"865d1447",7899:"5bfbde57",7900:"4c76b531",7924:"0a02aff3",8160:"2aa9b0dd",8209:"01a85c17",8221:"40e30cb7",8245:"d9683343",8271:"b9d5de69",8401:"17896441",8455:"eb35abdd",8460:"fc323215",8476:"f6b2bbb1",8519:"7c823085",8560:"0970ca0c",8581:"935f2afb",8620:"1c323773",8634:"b6072cb9",8641:"3498d2db",8758:"f164116d",8768:"881ac13d",8795:"e237dedf",8839:"ea9c95e7",8877:"dbe4f0b7",8881:"3b089002",8952:"2cf03b61",9034:"9292650b",9118:"63371bf1",9120:"df8c2417",9304:"04c33c1d",9342:"7c97e9a2",9437:"17f9232f",9515:"5d2f6d16",9543:"4aba33e7",9582:"80f9953e",9585:"0b851b4d",9591:"1d245f66",9592:"5eff386f",9679:"239f6efa",9685:"181d712d",9812:"f999fa4a",9966:"0c7edd3c"}[e]||e)+"."+{2:"1f27e26a",53:"643705c4",68:"0d2ec23e",96:"faf569c2",99:"edd83f34",123:"1b9fa380",178:"280b9b1f",187:"f3d8b74a",235:"10138825",289:"4abf637f",298:"25266a56",323:"ee0f3976",332:"51e9bf3e",347:"7f4ce9c1",416:"05118c27",456:"093d089d",577:"be77af72",612:"a166cb62",641:"f6191334",643:"41d49cce",656:"e52bc8b4",682:"66d33fde",708:"db909afe",770:"901aae8b",1079:"7080559a",1138:"824b22b0",1211:"9b939b14",1218:"e7c74c88",1274:"35244da2",1513:"2c82f68a",1533:"fca19837",1539:"ae853ff9",1581:"4195c41d",1700:"f157ac87",1707:"c9fbdd0f",1774:"17e434df",1839:"dfefe3ec",1874:"e3cf7671",1987:"5b8550eb",1998:"bb9f1657",2043:"4b08afc9",2068:"c916ad98",2083:"9720322e",2120:"c378e6a7",2124:"de224e29",2138:"3f4480b7",2141:"909642c9",2154:"da472e58",2247:"de7dd6c3",2269:"a45011d9",2270:"71c74cf9",2338:"57d2e938",2373:"8afe65d7",2377:"1798461c",2419:"046f7024",2438:"957fafc1",2459:"890822eb",2460:"a0734a55",2496:"7e4d3165",2525:"5d62f296",2581:"1981e939",2634:"e1d944f6",2661:"e36a0578",2698:"2bc80b37",2707:"dae9d9d6",2711:"fa02942d",2905:"6b019ced",2920:"0ae10d4e",2965:"78401e17",3010:"fcb1dac4",3015:"c5b175f4",3022:"96a9572e",3103:"b3494d59",3119:"f5799d4a",3134:"c320ce7a",3150:"31216255",3182:"e872148c",3206:"f70d2090",3249:"05697243",3282:"f514eeda",3360:"b7fe54da",3372:"c76e1161",3379:"e3efaf98",3392:"30233b6b",3414:"8a59078e",3421:"919e88ab",3486:"4120424a",3559:"e797962c",3671:"c422c4ff",3818:"84929b86",3833:"96f6bd4c",3890:"a1e9bc3d",3892:"df277861",3894:"7118bdad",3995:"25a3fcd8",4005:"6ac49997",4034:"6e02f4ab",4078:"65e44e04",4098:"ba8e01d2",4099:"56c58255",4122:"611092dd",4196:"8bb50cb6",4203:"f17ba905",4216:"f1fa5def",4261:"039d9b7e",4324:"cb3ef21a",4368:"7a1707f4",4480:"67192b50",4511:"7596329b",4557:"4e05340b",4651:"0dcb9382",4778:"7739c121",4813:"858bb418",4871:"82fed3b6",4879:"f648c4cc",4930:"ee7974c5",5104:"7d2d644b",5110:"58b49f3b",5210:"43ddf441",5235:"66b0d9a6",5333:"2ce8bcd4",5351:"70a63a4b",5403:"6b6bcdc6",5458:"1e49bd17",5465:"c1a5338d",5484:"916290d1",5564:"fdb2b03a",5599:"feb62006",5763:"3883a9fe",5844:"a1bec739",5928:"e7b6bff9",5992:"9e140e08",6002:"a55b3bbe",6044:"57ef271f",6101:"16c7c282",6104:"7bedeeb2",6291:"aeb25c54",6388:"2afcabcc",6417:"6b5cfd35",6461:"298241f0",6527:"c4082127",6618:"16449148",6643:"6c9f1bae",6646:"9a120b1c",6769:"9d3b496e",6887:"de5f9600",6913:"b3eed543",6938:"175f49b8",6941:"b9946c6e",6990:"4e452f8b",7024:"83956665",7056:"4c3565bb",7109:"21d7a131",7126:"4254ddec",7153:"ef523078",7243:"a96191eb",7308:"6a4ec96e",7321:"28a3c9c6",7360:"0cb4ee98",7367:"c88270d2",7472:"1e97cc47",7473:"8e68df94",7529:"873b7d50",7577:"05962f98",7583:"47599a5e",7606:"bcb666eb",7608:"5aa43b26",7619:"6e16aad9",7643:"ceda7075",7696:"a36dbbbc",7714:"1a5f4bec",7715:"47b7048e",7742:"e2bbc433",7746:"da942ed4",7761:"866bd94c",7813:"0071775d",7839:"cdc3b49c",7846:"f970aa23",7886:"b3619613",7899:"4495bcb5",7900:"4c2ae5f9",7924:"86b8b3eb",8158:"4d82f1a9",8160:"1635a405",8209:"f9b0ff38",8221:"337380fd",8245:"6305ce29",8271:"5e1e4d58",8382:"dd3ca4bc",8401:"ab9f7aa1",8455:"e26bbdf6",8460:"bc04a210",8476:"0a7de365",8519:"773348c9",8560:"413aa791",8581:"53b559d9",8620:"7389ae93",8634:"f1f6f143",8641:"45e762a4",8758:"ea9aecd2",8768:"ebfed347",8795:"1aee74c9",8839:"bbbdc4fe",8877:"9ecfb22b",8881:"012e62b4",8913:"3eea0e0d",8952:"fc494d15",9034:"2e9752e5",9118:"a72526cb",9120:"6321b9e1",9304:"693e72fc",9342:"d7be4740",9437:"54824781",9515:"719d5572",9543:"2f0398d6",9551:"0760c9a4",9582:"f682bd67",9585:"6283e682",9591:"8fba063b",9592:"ed6cb595",9679:"b088b627",9685:"9d37d632",9812:"1e75f82d",9966:"6773f7e6"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),d={},b="website:",r.l=(e,f,a,c)=>{if(d[e])d[e].push(f);else{var t,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var b=d[e];if(delete d[e],t.parentNode&&t.parentNode.removeChild(t),b&&b.forEach((e=>e(a))),f)return f(a)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=u.bind(null,t.onerror),t.onload=u.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),r.p="/Detox/",r.gca=function(e){return e={17896441:"8401",24602229:"3892",26368098:"1211",96997528:"1138","18fc5556":"2",fe23c957:"53","268dddcf":"68","121cced5":"96","0a2b2829":"99","3bc305d8":"123","861bdbb6":"178","45e366b7":"187","85c7de33":"235","9a6ca8b8":"289",a4761f6f:"298",cb38042d:"323",e6cdcb35:"332","6fb4057b":"347",e9d07a0a:"456",d9894068:"577",b1c3f2c7:"612","86a9c212":"641","20110cf9":"643",d8b689b0:"656","1373a77b":"682","9509e94f":"708",df0b489b:"770","1c545b73":"1079","5bc90040":"1218",f02f7df4:"1274","8ab53c77":"1513","9a34b858":"1533","3fb5a56d":"1539","328083ea":"1581",fc9f0a8f:"1700",f431fa1f:"1707","66491fb6":"1839","5fc994c2":"1874",ecfe08ed:"1987","5133b137":"1998","4e8770ad":"2043","5d002dae":"2068","3d4c33f6":"2083","2dcc617a":"2120","0d1d5dba":"2124","1a4e3797":"2138","933bed1e":"2141","9017a355":"2154",af67069a:"2247","3f859f05":"2269",c62bfd53:"2270","7b8d824d":"2338","7a99341f":"2373","38bf2aac":"2377","9eb50243":"2419","4afa4b8a":"2438","649c4fa3":"2459","37fdb427":"2460","85addd09":"2496",d350cff3:"2525",dc701447:"2581",c4f5d8e4:"2634","435645a6":"2661","373c35af":"2698",c50db514:"2707","9e4087bc":"2711",ddab1e1f:"2905","398b3246":"2920","3e7ee0fc":"2965",a55aca30:"3010","0e97a833":"3015",e6bddadc:"3022",f2f4b8a7:"3103",f14c3b1d:"3119",e432d2f9:"3134","090a441b":"3150","6e1398c4":"3182","74a579df":"3206",ccc49370:"3249",c12557ec:"3360",b49de2ad:"3372",f97fefc4:"3379","0ef19a76":"3392","04d1378b":"3414",bab8a798:"3421",cae448e5:"3486","4898f926":"3559","3f3efe2b":"3671","7358fe14":"3818",c262e01e:"3833","06556991":"3890","120e2fab":"3894",beec6c9a:"3995","155ec335":"4005",fa42474c:"4034",e8bfc54f:"4078",bc243f9c:"4099",c8447d5b:"4122",f9078c13:"4196","9338ecde":"4203","50214cd4":"4216","66c89031":"4261","9b67fd78":"4324",ef7da448:"4368","4d2064e8":"4480",a6860cb2:"4511",c74a7097:"4557","040764b7":"4651",a622d695:"4778","6875c492":"4813","85a74db3":"4871","00424e3f":"4879","3f50474f":"4930","2ec4e639":"5104",ddb15f76:"5110","4a184f52":"5210",b48043f7:"5235","743699a6":"5333",df5bc064:"5351","684e0aea":"5403","5125c427":"5458",d389a7b2:"5465","30a967f3":"5484","5fcf77d7":"5564","3abc4359":"5599","851303a1":"5763","329947da":"5844",ae694851:"5928","3c59129e":"5992","6781c826":"6002",cd796466:"6044","750255a9":"6101","8e7c33d6":"6104","1ee22d16":"6291",c7632c1f:"6388","79ac7843":"6417",c83bcda6:"6461","32a9b7bf":"6527",a8a1de49:"6618","289d965c":"6643",e453af6e:"6646","1a01fdf1":"6769","19ee85cd":"6887","41e12717":"6913",b80a1ac9:"6938",f98b7248:"6941","8f43d633":"6990","0f6ee85b":"7024","40113ee6":"7056","54c48e38":"7109","5e26652b":"7126","6af18994":"7153","5a9cd6fb":"7243","48c4a2a0":"7308","23a61782":"7321",af952e90:"7360",ea7b1b31:"7367","814f3328":"7472","7accbb75":"7473","90ca9965":"7529","5b12c1a9":"7577",af558054:"7583","9f23071e":"7606","2d84255a":"7608","478bcf42":"7619",a6aa9e1f:"7643","3974811d":"7696","170ab94c":"7714","0b72a6ff":"7715",dcfd3b61:"7742","7dcc0419":"7746",ba1e31c2:"7761","656a21b7":"7813","7d2f6bd9":"7839",dd5377a3:"7846","865d1447":"7886","5bfbde57":"7899","4c76b531":"7900","0a02aff3":"7924","2aa9b0dd":"8160","01a85c17":"8209","40e30cb7":"8221",d9683343:"8245",b9d5de69:"8271",eb35abdd:"8455",fc323215:"8460",f6b2bbb1:"8476","7c823085":"8519","0970ca0c":"8560","935f2afb":"8581","1c323773":"8620",b6072cb9:"8634","3498d2db":"8641",f164116d:"8758","881ac13d":"8768",e237dedf:"8795",ea9c95e7:"8839",dbe4f0b7:"8877","3b089002":"8881","2cf03b61":"8952","9292650b":"9034","63371bf1":"9118",df8c2417:"9120","04c33c1d":"9304","7c97e9a2":"9342","17f9232f":"9437","5d2f6d16":"9515","4aba33e7":"9543","80f9953e":"9582","0b851b4d":"9585","1d245f66":"9591","5eff386f":"9592","239f6efa":"9679","181d712d":"9685",f999fa4a:"9812","0c7edd3c":"9966"}[e]||e,r.p+r.u(e)},(()=>{var e={5354:0,1869:0};r.f.j=(f,a)=>{var d=r.o(e,f)?e[f]:void 0;if(0!==d)if(d)a.push(d[2]);else if(/^(1869|5354)$/.test(f))e[f]=0;else{var b=new Promise(((a,b)=>d=e[f]=[a,b]));a.push(d[2]=b);var c=r.p+r.u(f),t=new Error;r.l(c,(a=>{if(r.o(e,f)&&(0!==(d=e[f])&&(e[f]=void 0),d)){var b=a&&("load"===a.type?"missing":a.type),c=a&&a.target&&a.target.src;t.message="Loading chunk "+f+" failed.\n("+b+": "+c+")",t.name="ChunkLoadError",t.type=b,t.request=c,d[1](t)}}),"chunk-"+f,f)}},r.O.j=f=>0===e[f];var f=(f,a)=>{var d,b,c=a[0],t=a[1],o=a[2],n=0;if(c.some((f=>0!==e[f]))){for(d in t)r.o(t,d)&&(r.m[d]=t[d]);if(o)var i=o(r)}for(f&&f(a);n{"use strict";var e,f,a,d,c,b={},t={};function r(e){var f=t[e];if(void 0!==f)return f.exports;var a=t[e]={id:e,loaded:!1,exports:{}};return b[e].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}r.m=b,e=[],r.O=(f,a,d,c)=>{if(!a){var b=1/0;for(i=0;i=c)&&Object.keys(r.O).every((e=>r.O[e](a[o])))?a.splice(o--,1):(t=!1,c0&&e[i-1][2]>c;i--)e[i]=e[i-1];e[i]=[a,d,c]},r.n=e=>{var f=e&&e.__esModule?()=>e.default:()=>e;return r.d(f,{a:f}),f},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var c=Object.create(null);r.r(c);var b={};f=f||[null,a({}),a([]),a(a)];for(var t=2&d&&e;"object"==typeof t&&!~f.indexOf(t);t=a(t))Object.getOwnPropertyNames(t).forEach((f=>b[f]=()=>e[f]));return b.default=()=>e,r.d(c,b),c},r.d=(e,f)=>{for(var a in f)r.o(f,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:f[a]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((f,a)=>(r.f[a](e,f),f)),[])),r.u=e=>"assets/js/"+({2:"18fc5556",53:"fe23c957",68:"268dddcf",96:"121cced5",99:"0a2b2829",123:"3bc305d8",178:"861bdbb6",187:"45e366b7",235:"85c7de33",289:"9a6ca8b8",298:"a4761f6f",323:"cb38042d",332:"e6cdcb35",347:"6fb4057b",456:"e9d07a0a",577:"d9894068",612:"b1c3f2c7",641:"86a9c212",643:"20110cf9",656:"d8b689b0",682:"1373a77b",708:"9509e94f",770:"df0b489b",1079:"1c545b73",1138:"96997528",1211:"26368098",1218:"5bc90040",1274:"f02f7df4",1513:"8ab53c77",1533:"9a34b858",1539:"3fb5a56d",1581:"328083ea",1700:"fc9f0a8f",1707:"f431fa1f",1839:"66491fb6",1874:"5fc994c2",1987:"ecfe08ed",1998:"5133b137",2043:"4e8770ad",2068:"5d002dae",2083:"3d4c33f6",2120:"2dcc617a",2124:"0d1d5dba",2138:"1a4e3797",2141:"933bed1e",2154:"9017a355",2247:"af67069a",2269:"3f859f05",2270:"c62bfd53",2338:"7b8d824d",2373:"7a99341f",2377:"38bf2aac",2419:"9eb50243",2438:"4afa4b8a",2459:"649c4fa3",2460:"37fdb427",2496:"85addd09",2525:"d350cff3",2581:"dc701447",2634:"c4f5d8e4",2661:"435645a6",2698:"373c35af",2707:"c50db514",2711:"9e4087bc",2905:"ddab1e1f",2920:"398b3246",2965:"3e7ee0fc",3010:"a55aca30",3015:"0e97a833",3022:"e6bddadc",3103:"f2f4b8a7",3119:"f14c3b1d",3134:"e432d2f9",3150:"090a441b",3182:"6e1398c4",3206:"74a579df",3249:"ccc49370",3360:"c12557ec",3372:"b49de2ad",3379:"f97fefc4",3392:"0ef19a76",3414:"04d1378b",3421:"bab8a798",3486:"cae448e5",3559:"4898f926",3671:"3f3efe2b",3818:"7358fe14",3833:"c262e01e",3890:"06556991",3892:"24602229",3894:"120e2fab",3995:"beec6c9a",4005:"155ec335",4034:"fa42474c",4078:"e8bfc54f",4099:"bc243f9c",4122:"c8447d5b",4196:"f9078c13",4203:"9338ecde",4216:"50214cd4",4261:"66c89031",4324:"9b67fd78",4368:"ef7da448",4480:"4d2064e8",4511:"a6860cb2",4557:"c74a7097",4651:"040764b7",4778:"a622d695",4813:"6875c492",4871:"85a74db3",4879:"00424e3f",4930:"3f50474f",5104:"2ec4e639",5110:"ddb15f76",5210:"4a184f52",5235:"b48043f7",5333:"743699a6",5351:"df5bc064",5403:"684e0aea",5458:"5125c427",5465:"d389a7b2",5484:"30a967f3",5564:"5fcf77d7",5599:"3abc4359",5763:"851303a1",5844:"329947da",5928:"ae694851",5992:"3c59129e",6002:"6781c826",6044:"cd796466",6101:"750255a9",6104:"8e7c33d6",6291:"1ee22d16",6388:"c7632c1f",6417:"79ac7843",6461:"c83bcda6",6527:"32a9b7bf",6618:"a8a1de49",6643:"289d965c",6646:"e453af6e",6769:"1a01fdf1",6887:"19ee85cd",6913:"41e12717",6938:"b80a1ac9",6941:"f98b7248",6990:"8f43d633",7024:"0f6ee85b",7056:"40113ee6",7109:"54c48e38",7126:"5e26652b",7153:"6af18994",7243:"5a9cd6fb",7308:"48c4a2a0",7321:"23a61782",7360:"af952e90",7367:"ea7b1b31",7472:"814f3328",7473:"7accbb75",7529:"90ca9965",7577:"5b12c1a9",7583:"af558054",7606:"9f23071e",7608:"2d84255a",7619:"478bcf42",7643:"a6aa9e1f",7696:"3974811d",7714:"170ab94c",7715:"0b72a6ff",7742:"dcfd3b61",7746:"7dcc0419",7761:"ba1e31c2",7813:"656a21b7",7839:"7d2f6bd9",7846:"dd5377a3",7886:"865d1447",7899:"5bfbde57",7900:"4c76b531",7924:"0a02aff3",8160:"2aa9b0dd",8209:"01a85c17",8221:"40e30cb7",8245:"d9683343",8271:"b9d5de69",8401:"17896441",8455:"eb35abdd",8460:"fc323215",8476:"f6b2bbb1",8519:"7c823085",8560:"0970ca0c",8581:"935f2afb",8620:"1c323773",8634:"b6072cb9",8641:"3498d2db",8758:"f164116d",8768:"881ac13d",8795:"e237dedf",8839:"ea9c95e7",8877:"dbe4f0b7",8881:"3b089002",8952:"2cf03b61",9034:"9292650b",9118:"63371bf1",9120:"df8c2417",9304:"04c33c1d",9342:"7c97e9a2",9437:"17f9232f",9515:"5d2f6d16",9543:"4aba33e7",9582:"80f9953e",9585:"0b851b4d",9591:"1d245f66",9592:"5eff386f",9679:"239f6efa",9685:"181d712d",9812:"f999fa4a",9966:"0c7edd3c"}[e]||e)+"."+{2:"1f27e26a",53:"643705c4",68:"0d2ec23e",96:"faf569c2",99:"edd83f34",123:"1b9fa380",178:"280b9b1f",187:"f3d8b74a",235:"10138825",289:"4abf637f",298:"25266a56",323:"ee0f3976",332:"51e9bf3e",347:"7f4ce9c1",416:"05118c27",456:"093d089d",577:"be77af72",612:"a166cb62",641:"f6191334",643:"41d49cce",656:"e52bc8b4",682:"66d33fde",708:"db909afe",770:"901aae8b",1079:"7080559a",1138:"824b22b0",1211:"9b939b14",1218:"e7c74c88",1274:"35244da2",1513:"2c82f68a",1533:"fca19837",1539:"ae853ff9",1581:"4195c41d",1700:"f157ac87",1707:"c359d024",1774:"17e434df",1839:"dfefe3ec",1874:"e3cf7671",1987:"5b8550eb",1998:"bb9f1657",2043:"4b08afc9",2068:"c916ad98",2083:"9720322e",2120:"c378e6a7",2124:"de224e29",2138:"3f4480b7",2141:"909642c9",2154:"da472e58",2247:"262c39d0",2269:"a45011d9",2270:"71c74cf9",2338:"57d2e938",2373:"8afe65d7",2377:"1798461c",2419:"046f7024",2438:"957fafc1",2459:"890822eb",2460:"a0734a55",2496:"7e4d3165",2525:"5d62f296",2581:"1981e939",2634:"e1d944f6",2661:"aed80a98",2698:"2bc80b37",2707:"dae9d9d6",2711:"fa02942d",2905:"6b019ced",2920:"0ae10d4e",2965:"78401e17",3010:"fcb1dac4",3015:"c5b175f4",3022:"96a9572e",3103:"b3494d59",3119:"f5799d4a",3134:"c320ce7a",3150:"31216255",3182:"e872148c",3206:"f70d2090",3249:"05697243",3282:"f514eeda",3360:"b7fe54da",3372:"c76e1161",3379:"e3efaf98",3392:"30233b6b",3414:"8a59078e",3421:"919e88ab",3486:"4120424a",3559:"e797962c",3671:"c422c4ff",3818:"84929b86",3833:"96f6bd4c",3890:"a1e9bc3d",3892:"df277861",3894:"7118bdad",3995:"25a3fcd8",4005:"6ac49997",4034:"6e02f4ab",4078:"65e44e04",4098:"ba8e01d2",4099:"56c58255",4122:"611092dd",4196:"8bb50cb6",4203:"f17ba905",4216:"f1fa5def",4261:"039d9b7e",4324:"cb3ef21a",4368:"7a1707f4",4480:"67192b50",4511:"7596329b",4557:"4e05340b",4651:"0dcb9382",4778:"7739c121",4813:"858bb418",4871:"82fed3b6",4879:"f648c4cc",4930:"ee7974c5",5104:"7d2d644b",5110:"58b49f3b",5210:"43ddf441",5235:"66b0d9a6",5333:"2ce8bcd4",5351:"70a63a4b",5403:"6b6bcdc6",5458:"1e49bd17",5465:"c1a5338d",5484:"916290d1",5564:"fdb2b03a",5599:"feb62006",5763:"3883a9fe",5844:"a1bec739",5928:"e7b6bff9",5992:"9e140e08",6002:"a55b3bbe",6044:"57ef271f",6101:"16c7c282",6104:"7bedeeb2",6291:"aeb25c54",6388:"2afcabcc",6417:"6b5cfd35",6461:"298241f0",6527:"c4082127",6618:"16449148",6643:"6c9f1bae",6646:"9a120b1c",6769:"9d3b496e",6887:"de5f9600",6913:"b3eed543",6938:"175f49b8",6941:"b9946c6e",6990:"4e452f8b",7024:"83956665",7056:"4c3565bb",7109:"21d7a131",7126:"4254ddec",7153:"ef523078",7243:"a96191eb",7308:"6a4ec96e",7321:"28a3c9c6",7360:"0cb4ee98",7367:"c88270d2",7472:"1e97cc47",7473:"8e68df94",7529:"873b7d50",7577:"05962f98",7583:"47599a5e",7606:"bcb666eb",7608:"5aa43b26",7619:"6e16aad9",7643:"ceda7075",7696:"a36dbbbc",7714:"1a5f4bec",7715:"47b7048e",7742:"e2bbc433",7746:"da942ed4",7761:"866bd94c",7813:"0071775d",7839:"cdc3b49c",7846:"f970aa23",7886:"b3619613",7899:"4495bcb5",7900:"4c2ae5f9",7924:"86b8b3eb",8158:"4d82f1a9",8160:"1635a405",8209:"f9b0ff38",8221:"337380fd",8245:"6305ce29",8271:"5e1e4d58",8382:"dd3ca4bc",8401:"ab9f7aa1",8455:"e26bbdf6",8460:"bc04a210",8476:"0a7de365",8519:"773348c9",8560:"413aa791",8581:"53b559d9",8620:"7389ae93",8634:"f1f6f143",8641:"45e762a4",8758:"ea9aecd2",8768:"ebfed347",8795:"1aee74c9",8839:"bbbdc4fe",8877:"9ecfb22b",8881:"012e62b4",8913:"3eea0e0d",8952:"fc494d15",9034:"2e9752e5",9118:"a72526cb",9120:"6321b9e1",9304:"693e72fc",9342:"d7be4740",9437:"54824781",9515:"719d5572",9543:"2f0398d6",9551:"0760c9a4",9582:"f682bd67",9585:"6283e682",9591:"8fba063b",9592:"ed6cb595",9679:"b088b627",9685:"9d37d632",9812:"1e75f82d",9966:"6773f7e6"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),d={},c="website:",r.l=(e,f,a,b)=>{if(d[e])d[e].push(f);else{var t,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var c=d[e];if(delete d[e],t.parentNode&&t.parentNode.removeChild(t),c&&c.forEach((e=>e(a))),f)return f(a)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=u.bind(null,t.onerror),t.onload=u.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),r.p="/Detox/",r.gca=function(e){return e={17896441:"8401",24602229:"3892",26368098:"1211",96997528:"1138","18fc5556":"2",fe23c957:"53","268dddcf":"68","121cced5":"96","0a2b2829":"99","3bc305d8":"123","861bdbb6":"178","45e366b7":"187","85c7de33":"235","9a6ca8b8":"289",a4761f6f:"298",cb38042d:"323",e6cdcb35:"332","6fb4057b":"347",e9d07a0a:"456",d9894068:"577",b1c3f2c7:"612","86a9c212":"641","20110cf9":"643",d8b689b0:"656","1373a77b":"682","9509e94f":"708",df0b489b:"770","1c545b73":"1079","5bc90040":"1218",f02f7df4:"1274","8ab53c77":"1513","9a34b858":"1533","3fb5a56d":"1539","328083ea":"1581",fc9f0a8f:"1700",f431fa1f:"1707","66491fb6":"1839","5fc994c2":"1874",ecfe08ed:"1987","5133b137":"1998","4e8770ad":"2043","5d002dae":"2068","3d4c33f6":"2083","2dcc617a":"2120","0d1d5dba":"2124","1a4e3797":"2138","933bed1e":"2141","9017a355":"2154",af67069a:"2247","3f859f05":"2269",c62bfd53:"2270","7b8d824d":"2338","7a99341f":"2373","38bf2aac":"2377","9eb50243":"2419","4afa4b8a":"2438","649c4fa3":"2459","37fdb427":"2460","85addd09":"2496",d350cff3:"2525",dc701447:"2581",c4f5d8e4:"2634","435645a6":"2661","373c35af":"2698",c50db514:"2707","9e4087bc":"2711",ddab1e1f:"2905","398b3246":"2920","3e7ee0fc":"2965",a55aca30:"3010","0e97a833":"3015",e6bddadc:"3022",f2f4b8a7:"3103",f14c3b1d:"3119",e432d2f9:"3134","090a441b":"3150","6e1398c4":"3182","74a579df":"3206",ccc49370:"3249",c12557ec:"3360",b49de2ad:"3372",f97fefc4:"3379","0ef19a76":"3392","04d1378b":"3414",bab8a798:"3421",cae448e5:"3486","4898f926":"3559","3f3efe2b":"3671","7358fe14":"3818",c262e01e:"3833","06556991":"3890","120e2fab":"3894",beec6c9a:"3995","155ec335":"4005",fa42474c:"4034",e8bfc54f:"4078",bc243f9c:"4099",c8447d5b:"4122",f9078c13:"4196","9338ecde":"4203","50214cd4":"4216","66c89031":"4261","9b67fd78":"4324",ef7da448:"4368","4d2064e8":"4480",a6860cb2:"4511",c74a7097:"4557","040764b7":"4651",a622d695:"4778","6875c492":"4813","85a74db3":"4871","00424e3f":"4879","3f50474f":"4930","2ec4e639":"5104",ddb15f76:"5110","4a184f52":"5210",b48043f7:"5235","743699a6":"5333",df5bc064:"5351","684e0aea":"5403","5125c427":"5458",d389a7b2:"5465","30a967f3":"5484","5fcf77d7":"5564","3abc4359":"5599","851303a1":"5763","329947da":"5844",ae694851:"5928","3c59129e":"5992","6781c826":"6002",cd796466:"6044","750255a9":"6101","8e7c33d6":"6104","1ee22d16":"6291",c7632c1f:"6388","79ac7843":"6417",c83bcda6:"6461","32a9b7bf":"6527",a8a1de49:"6618","289d965c":"6643",e453af6e:"6646","1a01fdf1":"6769","19ee85cd":"6887","41e12717":"6913",b80a1ac9:"6938",f98b7248:"6941","8f43d633":"6990","0f6ee85b":"7024","40113ee6":"7056","54c48e38":"7109","5e26652b":"7126","6af18994":"7153","5a9cd6fb":"7243","48c4a2a0":"7308","23a61782":"7321",af952e90:"7360",ea7b1b31:"7367","814f3328":"7472","7accbb75":"7473","90ca9965":"7529","5b12c1a9":"7577",af558054:"7583","9f23071e":"7606","2d84255a":"7608","478bcf42":"7619",a6aa9e1f:"7643","3974811d":"7696","170ab94c":"7714","0b72a6ff":"7715",dcfd3b61:"7742","7dcc0419":"7746",ba1e31c2:"7761","656a21b7":"7813","7d2f6bd9":"7839",dd5377a3:"7846","865d1447":"7886","5bfbde57":"7899","4c76b531":"7900","0a02aff3":"7924","2aa9b0dd":"8160","01a85c17":"8209","40e30cb7":"8221",d9683343:"8245",b9d5de69:"8271",eb35abdd:"8455",fc323215:"8460",f6b2bbb1:"8476","7c823085":"8519","0970ca0c":"8560","935f2afb":"8581","1c323773":"8620",b6072cb9:"8634","3498d2db":"8641",f164116d:"8758","881ac13d":"8768",e237dedf:"8795",ea9c95e7:"8839",dbe4f0b7:"8877","3b089002":"8881","2cf03b61":"8952","9292650b":"9034","63371bf1":"9118",df8c2417:"9120","04c33c1d":"9304","7c97e9a2":"9342","17f9232f":"9437","5d2f6d16":"9515","4aba33e7":"9543","80f9953e":"9582","0b851b4d":"9585","1d245f66":"9591","5eff386f":"9592","239f6efa":"9679","181d712d":"9685",f999fa4a:"9812","0c7edd3c":"9966"}[e]||e,r.p+r.u(e)},(()=>{var e={5354:0,1869:0};r.f.j=(f,a)=>{var d=r.o(e,f)?e[f]:void 0;if(0!==d)if(d)a.push(d[2]);else if(/^(1869|5354)$/.test(f))e[f]=0;else{var c=new Promise(((a,c)=>d=e[f]=[a,c]));a.push(d[2]=c);var b=r.p+r.u(f),t=new Error;r.l(b,(a=>{if(r.o(e,f)&&(0!==(d=e[f])&&(e[f]=void 0),d)){var c=a&&("load"===a.type?"missing":a.type),b=a&&a.target&&a.target.src;t.message="Loading chunk "+f+" failed.\n("+c+": "+b+")",t.name="ChunkLoadError",t.type=c,t.request=b,d[1](t)}}),"chunk-"+f,f)}},r.O.j=f=>0===e[f];var f=(f,a)=>{var d,c,b=a[0],t=a[1],o=a[2],n=0;if(b.some((f=>0!==e[f]))){for(d in t)r.o(t,d)&&(r.m[d]=t[d]);if(o)var i=o(r)}for(f&&f(a);n