diff --git a/404.html b/404.html index 9a73860e88..0fad370f64 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/20110cf9.41d49cce.js b/assets/js/20110cf9.41d49cce.js deleted file mode 100644 index 67bdfbbb5b..0000000000 --- a/assets/js/20110cf9.41d49cce.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[643],{15680:(e,n,t)=>{t.d(n,{xA:()=>p,yg:()=>u});var a=t(96540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var g=a.createContext({}),s=function(e){var n=a.useContext(g),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},p=function(e){var n=s(e.components);return a.createElement(g.Provider,{value:n},e.children)},d="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,o=e.mdxType,i=e.originalType,g=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),d=s(t),c=o,u=d["".concat(g,".").concat(c)]||d[c]||m[c]||i;return t?a.createElement(u,r(r({ref:n},p),{},{components:t})):a.createElement(u,r({ref:n},p))}));function u(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var i=t.length,r=new Array(i);r[0]=c;var l={};for(var g in n)hasOwnProperty.call(n,g)&&(l[g]=n[g]);l.originalType=e,l[d]="string"==typeof e?e:o,r[1]=l;for(var s=2;s{t.r(n),t.d(n,{assets:()=>g,contentTitle:()=>r,default:()=>m,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var a=t(58168),o=(t(96540),t(15680));const i={},r="Logger",l={unversionedId:"api/logger",id:"version-20.x/api/logger",title:"Logger",description:"Detox Logger API allows you to save your custom messages and events alongside the built-in ones.",source:"@site/versioned_docs/version-20.x/api/logger.mdx",sourceDirName:"api",slug:"/api/logger",permalink:"/Detox/docs/api/logger",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/api/logger.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"apiSidebar",previous:{title:"System",permalink:"/Detox/docs/api/system"},next:{title:"Internals API",permalink:"/Detox/docs/api/internals"}},g={},s=[{value:"Properties",id:"properties",level:2},{value:"log.level [enum]",id:"loglevel-enum",level:3},{value:"Methods",id:"methods",level:2},{value:"log.*([event,] ...msg)",id:"logevent-msg",level:3},{value:"log.*.begin([event,] msg)",id:"logbeginevent-msg",level:3},{value:"log.*.end([event, msg])",id:"logendevent-msg",level:3},{value:"log.*.complete([event,] msg, functionOrPromise)",id:"logcompleteevent-msg-functionorpromise",level:3},{value:"Event metadata",id:"event-metadata",level:2},{value:"id [string | number]",id:"id-string--number",level:3},{value:"cat [string | string[]]",id:"cat-string--string",level:3},{value:"cname [string]",id:"cname-string",level:3},{value:"* [string | number | boolean]",id:"-string--number--boolean",level:3},{value:"Artifacts",id:"artifacts",level:2},{value:"detox.log",id:"detoxlog",level:3},{value:"detox.trace.json",id:"detoxtracejson",level:3}],p={toc:s},d="wrapper";function m(e){let{components:n,...i}=e;return(0,o.yg)(d,(0,a.A)({},p,i,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("h1",{id:"logger"},"Logger"),(0,o.yg)("p",null,"Detox Logger API allows you to save your custom messages and events alongside the built-in ones.\nIn addition to being formatted and printed to the console, they can be preserved as log artifacts if you enable them via ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test"},(0,o.yg)("inlineCode",{parentName:"a"},"--record-logs"))," CLI option or ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts#example"},(0,o.yg)("inlineCode",{parentName:"a"},"artifacts.plugins.log.enabled"))," in the config:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"/detox.log")," \u2013 plain text log;"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"/detox.trace.json")," \u2013 ",(0,o.yg)("a",{parentName:"li",href:"https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview"},"Trace Event format")," file, viewable via ",(0,o.yg)("a",{parentName:"li",href:"https://ui.perfetto.dev"},"Perfetto")," or ",(0,o.yg)("inlineCode",{parentName:"li"},"chrome://tracing"),".")),(0,o.yg)("p",null,(0,o.yg)("img",{src:t(57415).A,width:"1176",height:"218"})),(0,o.yg)("p",null,"Below we\u2019ll be listing all the public properties and methods of the logger."),(0,o.yg)("h2",{id:"properties"},"Properties"),(0,o.yg)("h3",{id:"loglevel-enum"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.level")," ","[","enum]"),(0,o.yg)("p",null,(0,o.yg)("strong",{parentName:"p"},"Read-only.")," Returns the current ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/logger#loggerlevel-enum"},"log level"),", one of values:\n",(0,o.yg)("inlineCode",{parentName:"p"},"fatal"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"error"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"warn"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"info"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"debug"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"trace"),"."),(0,o.yg)("h2",{id:"methods"},"Methods"),(0,o.yg)("h3",{id:"logevent-msg"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*([event,] ...msg)")),(0,o.yg)("p",null,"Logs an instant message with an optional ",(0,o.yg)("a",{parentName:"p",href:"#event-metadata"},"event metadata"),".\nThere are six methods for producing log messages varying by log level:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.fatal([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.error([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.warn([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.info([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.debug([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.trace([event,] ...msg)"))),(0,o.yg)("p",null,"Example:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"const { log } = require('detox');\n\nlog.info('Some message');\n// detox[2020] i Some message\n\nlog.error({ err: new Error('Test') }, 'An error message');\n// detox[2020] i An error message\n// err: Test\n")),(0,o.yg)("h3",{id:"logbeginevent-msg"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*.begin([event,] msg)")),(0,o.yg)("p",null,"Logs a beginning of a ",(0,o.yg)("em",{parentName:"p"},"duration event")," with an optional ",(0,o.yg)("a",{parentName:"p",href:"#event-metadata"},"event metadata"),".\nDuration events are displayed as continuous colorful segments on the timeline and can be stacked if you\ncall the method multiple times, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info.begin({ data: { email } }, 'Login Flow');\n// detox[2020] B Login Flow\n// data: { email: 'tester@example.com' }\nlog.info.begin('Nested sub-flow');\n// detox[2020] B Nested sub-flow\n")),(0,o.yg)("p",null,"Make sure you always end your events so that they don't get marked as unfinished, like shown on the screenshot below:"),(0,o.yg)("p",null,(0,o.yg)("img",{src:t(29104).A,width:"660",height:"632"})),(0,o.yg)("h3",{id:"logendevent-msg"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*.end([event, msg])")),(0,o.yg)("p",null,"Logs an end of a duration event with an optional ",(0,o.yg)("a",{parentName:"p",href:"#event-metadata"},"event metadata")," and a message. If the message is omitted,\nthe logger prints the corresponding message from the duration begin event:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info.end();\n// detox[2020] E Nested sub-flow\nlog.info.end({ success: true }, 'Login Flow (custom end message)');\n// detox[2020] E Login Flow (custom end message)\n")),(0,o.yg)("h3",{id:"logcompleteevent-msg-functionorpromise"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*.complete([event,] msg, functionOrPromise)")),(0,o.yg)("p",null,"A convenience method to wrap functions and promises with ",(0,o.yg)("a",{parentName:"p",href:"#logbeginevent-msg"},(0,o.yg)("inlineCode",{parentName:"a"},"log.*.begin"))," and ",(0,o.yg)("a",{parentName:"p",href:"#logendevent-msg"},(0,o.yg)("inlineCode",{parentName:"a"},"log.*.end")),", e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"await log.info.complete('Login Flow', async () => {\n // \u2026\n});\n// detox[2020] B Login Flow\n// detox[2020] E Login Flow\n")),(0,o.yg)("p",null,"As a bonus, this wrapper also adds ",(0,o.yg)("inlineCode",{parentName:"p"},"{ success: true }")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"{ success: false, error }")," metadata to\nthe end event."),(0,o.yg)("p",null,"Effectively, ",(0,o.yg)("inlineCode",{parentName:"p"},"begin")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"end")," can even be called in two complete different places - such as ",(0,o.yg)("inlineCode",{parentName:"p"},"beforeEach")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"afterEach"),", but that is discouraged."),(0,o.yg)("p",null,"In fact, ",(0,o.yg)("inlineCode",{parentName:"p"},"log.*.complete()")," is the recommended way of tracing things, e.g.:"),(0,o.yg)("h2",{id:"event-metadata"},"Event metadata"),(0,o.yg)("p",null,"All the log methods accept an optional first argument which can contain some custom\nmetadata: numbers, strings and booleans:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"detox.log.info({ /* metadata */ }, message);\ndetox.log.trace.begin({ /* metadata */ }, message);\ndetox.log.trace.end({ /* metadata */ });\n")),(0,o.yg)("p",null,"Aside from custom user properties, there are a few meaningful properties that affect the timeline representation."),(0,o.yg)("h3",{id:"id-string--number"},(0,o.yg)("inlineCode",{parentName:"h3"},"id")," ","[","string ","|"," number]"),(0,o.yg)("p",null,"Use arbitrary IDs when you have a risk of overlapping concurrent events, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"await Promise.all([\n await detox.log.complete({ id: 1 }, 'Do this', async () => { /* \u2026 */ }),\n await detox.log.complete({ id: 2 }, 'Do that', async () => { /* \u2026 */ }),\n]);\n")),(0,o.yg)("p",null,"Using IDs will prevent situations like this, where the nested event outlasts its parent:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"B E\n|-- event 1 ----|\n B E\n |-- event 2 -----|\n")),(0,o.yg)("p",null,"In the example above, the actual sequence of calls will be:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info.begin('event 1');\nlog.info.begin('event 2');\nlog.info.end(); // from event 1\nlog.info.end(); // from event 2\n")),(0,o.yg)("p",null,"Therefore, it will be interpreted erroneously on the timeline, as if the second event has ended before the first one:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"B E\n|-- event 1 --------|\n B E\n |-- event 2 -|\n")),(0,o.yg)("p",null,"When you begin an event with a specific ",(0,o.yg)("inlineCode",{parentName:"p"},"id"),' while there\'s already some other duration event, the logger allocates another "lane" for that event by assigning a distinct ',(0,o.yg)("inlineCode",{parentName:"p"},"tid")," (thread ID) to it:"),(0,o.yg)("p",null,(0,o.yg)("img",{src:t(94551).A,width:"574",height:"321"})),(0,o.yg)("h3",{id:"cat-string--string"},(0,o.yg)("inlineCode",{parentName:"h3"},"cat")," ","[","string ","|"," string","[","]]"),(0,o.yg)("p",null,"Event category. Helpful for filtering specific events."),(0,o.yg)("p",null,"Pass either a string of comma-separated values or a string array, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info({ cat: 'login,login-email' }, 'Starting e-mail login flow...');\n// is identical to:\nlog.info({ cat: ['login', 'login-email'] }, 'Starting e-mail login flow...');\n")),(0,o.yg)("h3",{id:"cname-string"},(0,o.yg)("inlineCode",{parentName:"h3"},"cname")," ","[","string]"),(0,o.yg)("p",null,"Custom event color. See the available color names ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/catapult-project/catapult/blob/main/tracing/tracing/base/color_scheme.html"},"here"),"."),(0,o.yg)("h3",{id:"-string--number--boolean"},(0,o.yg)("inlineCode",{parentName:"h3"},"*")," ","[","string ","|"," number ","|"," boolean]"),(0,o.yg)("p",null,"Your custom properties, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"detox.log.info({ login: 'test@example.com' }, 'Entering credentials...');\n")),(0,o.yg)("p",null,"Custom properties are not printed to the terminal logs, but there are a few reserved names which have\nan extra formatting due to our default ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/logger#loggeroptions-bunyandebugstreamoptions"},(0,o.yg)("inlineCode",{parentName:"a"},"logger.options.stringifiers"))," \u2014 these are: ",(0,o.yg)("inlineCode",{parentName:"p"},"args"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"data"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"error"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"stack"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"origin"),"."),(0,o.yg)("p",null,"Also, there are a few reserved properties which cannot be logged:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"pid")," \u2014 process ID,"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"tid")," \u2014 thread ID,"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"ts")," \u2014 timestamp,"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"ph")," \u2014 phase: ",(0,o.yg)("em",{parentName:"li"},"begin")," (B), ",(0,o.yg)("em",{parentName:"li"},"end")," (E), ",(0,o.yg)("em",{parentName:"li"},"instant")," (i) event.")),(0,o.yg)("h2",{id:"artifacts"},"Artifacts"),(0,o.yg)("p",null,"The logger subsystem produces two artifacts when ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test"},(0,o.yg)("inlineCode",{parentName:"a"},"--record-logs"))," CLI option is enabled or ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts#example"},(0,o.yg)("inlineCode",{parentName:"a"},"artifacts.plugins.log.enabled"))," config is set to true."),(0,o.yg)("h3",{id:"detoxlog"},(0,o.yg)("inlineCode",{parentName:"h3"},"detox.log")),(0,o.yg)("p",null,"This file contains all the log messages you could see in the terminal window, except that there are messages of all the log levels, from ",(0,o.yg)("inlineCode",{parentName:"p"},"fatal")," to ",(0,o.yg)("inlineCode",{parentName:"p"},"trace"),"."),(0,o.yg)("h3",{id:"detoxtracejson"},(0,o.yg)("inlineCode",{parentName:"h3"},"detox.trace.json")),(0,o.yg)("p",null,"JSON file, which, if loaded into ",(0,o.yg)("a",{parentName:"p",href:"https://ui.perfetto.dev"},"Perfetto")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"chrome://tracing")," (internal address in Google Chrome browser), would look something like this:"),(0,o.yg)("p",null,(0,o.yg)("img",{alt:"Timeline artifact example",src:t(67072).A,width:"1760",height:"523"})),(0,o.yg)("p",null,"The tracing view provides a visual, hierarchical representation of the various processes that took place during the execution of the testing session, over the execution\u2019s ",(0,o.yg)("em",{parentName:"p"},"time-line"),". These processes appear as hierarchical ",(0,o.yg)("em",{parentName:"p"},"sections")," \u2013 sometimes visually ordered in a parent-child way, depending on their formation time and context."))}m.isMDXComponent=!0},57415:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/log-example-07a2afd13f5a9c00f864226c5694c431.png"},29104:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/timeline-artifact-unfinished-08dd500d605b4fd3b02f4c8e9c32b5ad.png"},67072:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/timeline-artifact-330d8f1de5146484a15c9f5c9f4d9709.png"},94551:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/timeline-tid-83e361d5657a544fcc397f62686a8624.png"}}]); \ No newline at end of file diff --git a/assets/js/20110cf9.ee41fcb2.js b/assets/js/20110cf9.ee41fcb2.js new file mode 100644 index 0000000000..4a778efeea --- /dev/null +++ b/assets/js/20110cf9.ee41fcb2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[643],{15680:(e,n,t)=>{t.d(n,{xA:()=>p,yg:()=>u});var a=t(96540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var g=a.createContext({}),s=function(e){var n=a.useContext(g),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},p=function(e){var n=s(e.components);return a.createElement(g.Provider,{value:n},e.children)},d="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,o=e.mdxType,i=e.originalType,g=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),d=s(t),c=o,u=d["".concat(g,".").concat(c)]||d[c]||m[c]||i;return t?a.createElement(u,r(r({ref:n},p),{},{components:t})):a.createElement(u,r({ref:n},p))}));function u(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var i=t.length,r=new Array(i);r[0]=c;var l={};for(var g in n)hasOwnProperty.call(n,g)&&(l[g]=n[g]);l.originalType=e,l[d]="string"==typeof e?e:o,r[1]=l;for(var s=2;s{t.r(n),t.d(n,{assets:()=>g,contentTitle:()=>r,default:()=>m,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var a=t(58168),o=(t(96540),t(15680));const i={},r="Logger",l={unversionedId:"api/logger",id:"version-20.x/api/logger",title:"Logger",description:"Detox Logger API allows you to save your custom messages and events alongside the built-in ones.",source:"@site/versioned_docs/version-20.x/api/logger.mdx",sourceDirName:"api",slug:"/api/logger",permalink:"/Detox/docs/api/logger",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/api/logger.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"apiSidebar",previous:{title:"System",permalink:"/Detox/docs/api/system"},next:{title:"Detox Copilot",permalink:"/Detox/docs/api/copilot"}},g={},s=[{value:"Properties",id:"properties",level:2},{value:"log.level [enum]",id:"loglevel-enum",level:3},{value:"Methods",id:"methods",level:2},{value:"log.*([event,] ...msg)",id:"logevent-msg",level:3},{value:"log.*.begin([event,] msg)",id:"logbeginevent-msg",level:3},{value:"log.*.end([event, msg])",id:"logendevent-msg",level:3},{value:"log.*.complete([event,] msg, functionOrPromise)",id:"logcompleteevent-msg-functionorpromise",level:3},{value:"Event metadata",id:"event-metadata",level:2},{value:"id [string | number]",id:"id-string--number",level:3},{value:"cat [string | string[]]",id:"cat-string--string",level:3},{value:"cname [string]",id:"cname-string",level:3},{value:"* [string | number | boolean]",id:"-string--number--boolean",level:3},{value:"Artifacts",id:"artifacts",level:2},{value:"detox.log",id:"detoxlog",level:3},{value:"detox.trace.json",id:"detoxtracejson",level:3}],p={toc:s},d="wrapper";function m(e){let{components:n,...i}=e;return(0,o.yg)(d,(0,a.A)({},p,i,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("h1",{id:"logger"},"Logger"),(0,o.yg)("p",null,"Detox Logger API allows you to save your custom messages and events alongside the built-in ones.\nIn addition to being formatted and printed to the console, they can be preserved as log artifacts if you enable them via ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test"},(0,o.yg)("inlineCode",{parentName:"a"},"--record-logs"))," CLI option or ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts#example"},(0,o.yg)("inlineCode",{parentName:"a"},"artifacts.plugins.log.enabled"))," in the config:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"/detox.log")," \u2013 plain text log;"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"/detox.trace.json")," \u2013 ",(0,o.yg)("a",{parentName:"li",href:"https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview"},"Trace Event format")," file, viewable via ",(0,o.yg)("a",{parentName:"li",href:"https://ui.perfetto.dev"},"Perfetto")," or ",(0,o.yg)("inlineCode",{parentName:"li"},"chrome://tracing"),".")),(0,o.yg)("p",null,(0,o.yg)("img",{src:t(57415).A,width:"1176",height:"218"})),(0,o.yg)("p",null,"Below we\u2019ll be listing all the public properties and methods of the logger."),(0,o.yg)("h2",{id:"properties"},"Properties"),(0,o.yg)("h3",{id:"loglevel-enum"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.level")," ","[","enum]"),(0,o.yg)("p",null,(0,o.yg)("strong",{parentName:"p"},"Read-only.")," Returns the current ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/logger#loggerlevel-enum"},"log level"),", one of values:\n",(0,o.yg)("inlineCode",{parentName:"p"},"fatal"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"error"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"warn"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"info"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"debug"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"trace"),"."),(0,o.yg)("h2",{id:"methods"},"Methods"),(0,o.yg)("h3",{id:"logevent-msg"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*([event,] ...msg)")),(0,o.yg)("p",null,"Logs an instant message with an optional ",(0,o.yg)("a",{parentName:"p",href:"#event-metadata"},"event metadata"),".\nThere are six methods for producing log messages varying by log level:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.fatal([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.error([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.warn([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.info([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.debug([event,] ...msg)")),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"log.trace([event,] ...msg)"))),(0,o.yg)("p",null,"Example:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"const { log } = require('detox');\n\nlog.info('Some message');\n// detox[2020] i Some message\n\nlog.error({ err: new Error('Test') }, 'An error message');\n// detox[2020] i An error message\n// err: Test\n")),(0,o.yg)("h3",{id:"logbeginevent-msg"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*.begin([event,] msg)")),(0,o.yg)("p",null,"Logs a beginning of a ",(0,o.yg)("em",{parentName:"p"},"duration event")," with an optional ",(0,o.yg)("a",{parentName:"p",href:"#event-metadata"},"event metadata"),".\nDuration events are displayed as continuous colorful segments on the timeline and can be stacked if you\ncall the method multiple times, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info.begin({ data: { email } }, 'Login Flow');\n// detox[2020] B Login Flow\n// data: { email: 'tester@example.com' }\nlog.info.begin('Nested sub-flow');\n// detox[2020] B Nested sub-flow\n")),(0,o.yg)("p",null,"Make sure you always end your events so that they don't get marked as unfinished, like shown on the screenshot below:"),(0,o.yg)("p",null,(0,o.yg)("img",{src:t(29104).A,width:"660",height:"632"})),(0,o.yg)("h3",{id:"logendevent-msg"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*.end([event, msg])")),(0,o.yg)("p",null,"Logs an end of a duration event with an optional ",(0,o.yg)("a",{parentName:"p",href:"#event-metadata"},"event metadata")," and a message. If the message is omitted,\nthe logger prints the corresponding message from the duration begin event:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info.end();\n// detox[2020] E Nested sub-flow\nlog.info.end({ success: true }, 'Login Flow (custom end message)');\n// detox[2020] E Login Flow (custom end message)\n")),(0,o.yg)("h3",{id:"logcompleteevent-msg-functionorpromise"},(0,o.yg)("inlineCode",{parentName:"h3"},"log.*.complete([event,] msg, functionOrPromise)")),(0,o.yg)("p",null,"A convenience method to wrap functions and promises with ",(0,o.yg)("a",{parentName:"p",href:"#logbeginevent-msg"},(0,o.yg)("inlineCode",{parentName:"a"},"log.*.begin"))," and ",(0,o.yg)("a",{parentName:"p",href:"#logendevent-msg"},(0,o.yg)("inlineCode",{parentName:"a"},"log.*.end")),", e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"await log.info.complete('Login Flow', async () => {\n // \u2026\n});\n// detox[2020] B Login Flow\n// detox[2020] E Login Flow\n")),(0,o.yg)("p",null,"As a bonus, this wrapper also adds ",(0,o.yg)("inlineCode",{parentName:"p"},"{ success: true }")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"{ success: false, error }")," metadata to\nthe end event."),(0,o.yg)("p",null,"Effectively, ",(0,o.yg)("inlineCode",{parentName:"p"},"begin")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"end")," can even be called in two complete different places - such as ",(0,o.yg)("inlineCode",{parentName:"p"},"beforeEach")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"afterEach"),", but that is discouraged."),(0,o.yg)("p",null,"In fact, ",(0,o.yg)("inlineCode",{parentName:"p"},"log.*.complete()")," is the recommended way of tracing things, e.g.:"),(0,o.yg)("h2",{id:"event-metadata"},"Event metadata"),(0,o.yg)("p",null,"All the log methods accept an optional first argument which can contain some custom\nmetadata: numbers, strings and booleans:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"detox.log.info({ /* metadata */ }, message);\ndetox.log.trace.begin({ /* metadata */ }, message);\ndetox.log.trace.end({ /* metadata */ });\n")),(0,o.yg)("p",null,"Aside from custom user properties, there are a few meaningful properties that affect the timeline representation."),(0,o.yg)("h3",{id:"id-string--number"},(0,o.yg)("inlineCode",{parentName:"h3"},"id")," ","[","string ","|"," number]"),(0,o.yg)("p",null,"Use arbitrary IDs when you have a risk of overlapping concurrent events, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"await Promise.all([\n await detox.log.complete({ id: 1 }, 'Do this', async () => { /* \u2026 */ }),\n await detox.log.complete({ id: 2 }, 'Do that', async () => { /* \u2026 */ }),\n]);\n")),(0,o.yg)("p",null,"Using IDs will prevent situations like this, where the nested event outlasts its parent:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"B E\n|-- event 1 ----|\n B E\n |-- event 2 -----|\n")),(0,o.yg)("p",null,"In the example above, the actual sequence of calls will be:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info.begin('event 1');\nlog.info.begin('event 2');\nlog.info.end(); // from event 1\nlog.info.end(); // from event 2\n")),(0,o.yg)("p",null,"Therefore, it will be interpreted erroneously on the timeline, as if the second event has ended before the first one:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"B E\n|-- event 1 --------|\n B E\n |-- event 2 -|\n")),(0,o.yg)("p",null,"When you begin an event with a specific ",(0,o.yg)("inlineCode",{parentName:"p"},"id"),' while there\'s already some other duration event, the logger allocates another "lane" for that event by assigning a distinct ',(0,o.yg)("inlineCode",{parentName:"p"},"tid")," (thread ID) to it:"),(0,o.yg)("p",null,(0,o.yg)("img",{src:t(94551).A,width:"574",height:"321"})),(0,o.yg)("h3",{id:"cat-string--string"},(0,o.yg)("inlineCode",{parentName:"h3"},"cat")," ","[","string ","|"," string","[","]]"),(0,o.yg)("p",null,"Event category. Helpful for filtering specific events."),(0,o.yg)("p",null,"Pass either a string of comma-separated values or a string array, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"log.info({ cat: 'login,login-email' }, 'Starting e-mail login flow...');\n// is identical to:\nlog.info({ cat: ['login', 'login-email'] }, 'Starting e-mail login flow...');\n")),(0,o.yg)("h3",{id:"cname-string"},(0,o.yg)("inlineCode",{parentName:"h3"},"cname")," ","[","string]"),(0,o.yg)("p",null,"Custom event color. See the available color names ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/catapult-project/catapult/blob/main/tracing/tracing/base/color_scheme.html"},"here"),"."),(0,o.yg)("h3",{id:"-string--number--boolean"},(0,o.yg)("inlineCode",{parentName:"h3"},"*")," ","[","string ","|"," number ","|"," boolean]"),(0,o.yg)("p",null,"Your custom properties, e.g.:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-js"},"detox.log.info({ login: 'test@example.com' }, 'Entering credentials...');\n")),(0,o.yg)("p",null,"Custom properties are not printed to the terminal logs, but there are a few reserved names which have\nan extra formatting due to our default ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/logger#loggeroptions-bunyandebugstreamoptions"},(0,o.yg)("inlineCode",{parentName:"a"},"logger.options.stringifiers"))," \u2014 these are: ",(0,o.yg)("inlineCode",{parentName:"p"},"args"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"data"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"error"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"stack"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"origin"),"."),(0,o.yg)("p",null,"Also, there are a few reserved properties which cannot be logged:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"pid")," \u2014 process ID,"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"tid")," \u2014 thread ID,"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"ts")," \u2014 timestamp,"),(0,o.yg)("li",{parentName:"ul"},(0,o.yg)("inlineCode",{parentName:"li"},"ph")," \u2014 phase: ",(0,o.yg)("em",{parentName:"li"},"begin")," (B), ",(0,o.yg)("em",{parentName:"li"},"end")," (E), ",(0,o.yg)("em",{parentName:"li"},"instant")," (i) event.")),(0,o.yg)("h2",{id:"artifacts"},"Artifacts"),(0,o.yg)("p",null,"The logger subsystem produces two artifacts when ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test"},(0,o.yg)("inlineCode",{parentName:"a"},"--record-logs"))," CLI option is enabled or ",(0,o.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts#example"},(0,o.yg)("inlineCode",{parentName:"a"},"artifacts.plugins.log.enabled"))," config is set to true."),(0,o.yg)("h3",{id:"detoxlog"},(0,o.yg)("inlineCode",{parentName:"h3"},"detox.log")),(0,o.yg)("p",null,"This file contains all the log messages you could see in the terminal window, except that there are messages of all the log levels, from ",(0,o.yg)("inlineCode",{parentName:"p"},"fatal")," to ",(0,o.yg)("inlineCode",{parentName:"p"},"trace"),"."),(0,o.yg)("h3",{id:"detoxtracejson"},(0,o.yg)("inlineCode",{parentName:"h3"},"detox.trace.json")),(0,o.yg)("p",null,"JSON file, which, if loaded into ",(0,o.yg)("a",{parentName:"p",href:"https://ui.perfetto.dev"},"Perfetto")," or ",(0,o.yg)("inlineCode",{parentName:"p"},"chrome://tracing")," (internal address in Google Chrome browser), would look something like this:"),(0,o.yg)("p",null,(0,o.yg)("img",{alt:"Timeline artifact example",src:t(67072).A,width:"1760",height:"523"})),(0,o.yg)("p",null,"The tracing view provides a visual, hierarchical representation of the various processes that took place during the execution of the testing session, over the execution\u2019s ",(0,o.yg)("em",{parentName:"p"},"time-line"),". These processes appear as hierarchical ",(0,o.yg)("em",{parentName:"p"},"sections")," \u2013 sometimes visually ordered in a parent-child way, depending on their formation time and context."))}m.isMDXComponent=!0},57415:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/log-example-07a2afd13f5a9c00f864226c5694c431.png"},29104:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/timeline-artifact-unfinished-08dd500d605b4fd3b02f4c8e9c32b5ad.png"},67072:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/timeline-artifact-330d8f1de5146484a15c9f5c9f4d9709.png"},94551:(e,n,t)=>{t.d(n,{A:()=>a});const a=t.p+"assets/images/timeline-tid-83e361d5657a544fcc397f62686a8624.png"}}]); \ No newline at end of file diff --git a/assets/js/232cf40d.248cb9e3.js b/assets/js/232cf40d.248cb9e3.js new file mode 100644 index 0000000000..16d0b2e678 --- /dev/null +++ b/assets/js/232cf40d.248cb9e3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[8077],{15680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>u});var o=n(96540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=o.createContext({}),s=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=s(e.components);return o.createElement(l.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},g=o.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),m=s(n),g=r,u=m["".concat(l,".").concat(g)]||m[g]||d[g]||a;return n?o.createElement(u,i(i({ref:t},c),{},{components:n})):o.createElement(u,i({ref:t},c))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,i=new Array(a);i[0]=g;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p[m]="string"==typeof e?e:r,i[1]=p;for(var s=2;s{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>d,frontMatter:()=>a,metadata:()=>p,toc:()=>s});var o=n(58168),r=(n(96540),n(15680));const a={},i="Detox Copilot",p={unversionedId:"api/copilot",id:"version-20.x/api/copilot",title:"Detox Copilot",description:"Detox Copilot is an AI-powered plugin that allows you to write Detox tests using natural language commands, powered by large language models (LLMs). It simplifies the process of writing end-to-end tests by translating human-readable instructions into Detox actions and assertions.",source:"@site/versioned_docs/version-20.x/api/copilot.md",sourceDirName:"api",slug:"/api/copilot",permalink:"/Detox/docs/api/copilot",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/api/copilot.md",tags:[],version:"20.x",frontMatter:{},sidebar:"apiSidebar",previous:{title:"Logger",permalink:"/Detox/docs/api/logger"},next:{title:"Internals API",permalink:"/Detox/docs/api/internals"}},l={},s=[{value:"Overview",id:"overview",level:2},{value:"Methods",id:"methods",level:2},{value:"copilot.init(promptHandler)",id:"copilotinitprompthandler",level:2},{value:"copilot.perform(...steps)",id:"copilotperformsteps",level:2},{value:"PromptHandler Interface",id:"prompthandler-interface",level:2}],c={toc:s},m="wrapper";function d(e){let{components:t,...n}=e;return(0,r.yg)(m,(0,o.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"detox-copilot"},"Detox Copilot"),(0,r.yg)("p",null,"Detox Copilot is an AI-powered plugin that allows you to write Detox tests using natural language commands, powered by large language models (LLMs). It simplifies the process of writing end-to-end tests by translating human-readable instructions into Detox actions and assertions."),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"},"Detox Copilot is based on a core library called ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"detox-copilot"),", which is designed for Detox but can be extended to work with other testing frameworks.")),(0,r.yg)("admonition",{title:"Work in Progress",type:"caution"},(0,r.yg)("p",{parentName:"admonition"},"Detox Copilot is in active development, and APIs are subject to change in future releases.")),(0,r.yg)("h2",{id:"overview"},"Overview"),(0,r.yg)("p",null,"Detox Copilot exposes a simple API that integrates seamlessly with your Detox tests. It requires minimal setup and allows you to perform complex testing operations by simply describing them in natural language."),(0,r.yg)("p",null,"For a more detailed guide on integrating Detox Copilot in your tests, refer to the ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/copilot/testing-with-copilot"},"Detox Copilot Guide"),"."),(0,r.yg)("h2",{id:"methods"},"Methods"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"#copilotinitprompthandler"},(0,r.yg)("inlineCode",{parentName:"a"},"copilot.init()"))),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"#copilotperformsteps"},(0,r.yg)("inlineCode",{parentName:"a"},"copilot.perform()")))),(0,r.yg)("h2",{id:"copilotinitprompthandler"},(0,r.yg)("inlineCode",{parentName:"h2"},"copilot.init(promptHandler)")),(0,r.yg)("p",null,"Initializes Detox Copilot with the given prompt handler. Must be called before any other Copilot methods."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameters:")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"promptHandler")," (PromptHandler): An object implementing the ",(0,r.yg)("a",{parentName:"li",href:"#prompthandler-interface"},(0,r.yg)("inlineCode",{parentName:"a"},"PromptHandler"))," interface.")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Example:")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-javascript"},"const { copilot } = require('detox');\nconst OpenAIPromptHandler = require('./OpenAIPromptHandler');\n\nbeforeAll(() => {\n const promptHandler = new OpenAIPromptHandler('YOUR_OPENAI_API_KEY');\n copilot.init(promptHandler);\n});\n")),(0,r.yg)("h2",{id:"copilotperformsteps"},(0,r.yg)("inlineCode",{parentName:"h2"},"copilot.perform(...steps)")),(0,r.yg)("p",null,"Performs a testing operation or series of operations based on the given steps."),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Parameters:")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"steps")," (string[]): One or more natural language instructions specifying the actions or assertions to perform.")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Returns:")),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"A promise that resolves when all steps have been executed.")),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Example:")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-javascript"},"await copilot.perform(\n 'Start the application',\n 'Tap on the \"Login\" button',\n 'Enter \"user@example.com\" into the email field',\n 'Enter \"password123\" into the password field',\n 'Press the \"Submit\" button',\n 'The welcome message \"Hello, User!\" should be displayed'\n);\n")),(0,r.yg)("h2",{id:"prompthandler-interface"},(0,r.yg)("inlineCode",{parentName:"h2"},"PromptHandler")," Interface"),(0,r.yg)("p",null,"The ",(0,r.yg)("inlineCode",{parentName:"p"},"PromptHandler")," interface defines how Detox Copilot communicates with the LLM service."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-typescript"},"interface PromptHandler {\n /**\n * Sends a prompt to the LLM service and returns the response.\n * @param prompt The prompt to send.\n * @param image Optional path to an image capturing the current UI state.\n * @returns A promise resolving to the LLM's response.\n */\n runPrompt(prompt: string, image?: string): Promise;\n\n /**\n * Indicates whether the LLM service supports snapshot images.\n * @returns A boolean value.\n */\n isSnapshotImageSupported(): boolean;\n}\n")),(0,r.yg)("p",null,"You can refer to the ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/copilot/testing-with-copilot"},"Detox Copilot Guide")," for an example of implementing a ",(0,r.yg)("inlineCode",{parentName:"p"},"PromptHandler")," for OpenAI's service."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/26160a2d.476f3a71.js b/assets/js/26160a2d.476f3a71.js new file mode 100644 index 0000000000..f6e09bd117 --- /dev/null +++ b/assets/js/26160a2d.476f3a71.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2961],{15680:(e,t,r)=>{r.d(t,{xA:()=>p,yg:()=>h});var a=r(96540);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}function i(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var s=a.createContext({}),c=function(e){var t=a.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},p=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,o=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),m=c(r),u=n,h=m["".concat(s,".").concat(u)]||m[u]||d[u]||o;return r?a.createElement(h,i(i({ref:t},p),{},{components:r})):a.createElement(h,i({ref:t},p))}));function h(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var o=r.length,i=new Array(o);i[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[m]="string"==typeof e?e:n,i[1]=l;for(var c=2;c{r.r(t),r.d(t,{assets:()=>ge,contentTitle:()=>ye,default:()=>we,frontMatter:()=>xe,metadata:()=>fe,toc:()=>ve});var a,n,o,i,l,s,c,p,m,d,u,h,x,y,f,g,v,E,b,w,k,N,C,O,D,M,P,z,j,T,L,S,I,A,R,B,G,F,U,H,W,X,V,Y,q,_,J,K,Q,Z,$,ee,te,re,ae,ne,oe,ie,le,se,ce,pe=r(58168),me=r(96540),de=r(15680);function ue(){return ue=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let{title:t,titleId:r,...pe}=e;return me.createElement("svg",ue({width:800,height:900,xmlns:"http://www.w3.org/2000/svg","aria-labelledby":r},pe),t?me.createElement("title",{id:r},t):null,a||(a=me.createElement("defs",null,me.createElement("marker",{id:"arrowhead",markerWidth:10,markerHeight:7,refX:5,refY:3.5,orient:"auto"},me.createElement("path",{fill:"#333",d:"m0 0 10 3.5L0 7z"})))),n||(n=me.createElement("style",null,".step-text,.sub-text{font-size:14px;text-anchor:middle;fill:#333;font-family:Arial,sans-serif}.sub-text{font-size:12px;fill:#555}")),o||(o=me.createElement("rect",{x:350,y:20,width:100,height:40,fill:"#d1eaff",stroke:"#333",rx:10,ry:10})),i||(i=me.createElement("text",{x:400,y:45,className:"step-text"},"Start")),l||(l=me.createElement("rect",{x:300,y:80,width:200,height:50,fill:"#e8f5e9",stroke:"#333",rx:10,ry:10})),s||(s=me.createElement("text",{x:400,y:105,className:"step-text"},"Gather Context")),c||(c=me.createElement("text",{x:400,y:120,className:"sub-text"},"(App's UI State)")),p||(p=me.createElement("rect",{x:300,y:160,width:200,height:50,fill:"#fff8e1",stroke:"#333",rx:10,ry:10})),m||(m=me.createElement("text",{x:400,y:185,className:"step-text"},"Check Cache")),d||(d=me.createElement("text",{x:400,y:200,className:"sub-text"},"(Based on intent)")),u||(u=me.createElement("rect",{x:300,y:230,width:200,height:50,fill:"#fff",stroke:"#333",rx:10,ry:10})),h||(h=me.createElement("text",{x:400,y:260,className:"step-text"},"Result in Cache?")),x||(x=me.createElement("rect",{x:50,y:320,width:200,height:50,fill:"#fff8e1",stroke:"#333",rx:10,ry:10})),y||(y=me.createElement("text",{x:150,y:345,className:"step-text"},"Retrieve Result")),f||(f=me.createElement("text",{x:150,y:360,className:"sub-text"},"(From Cache)")),g||(g=me.createElement("rect",{x:50,y:390,width:200,height:50,fill:"#c8e6c9",stroke:"#333",rx:10,ry:10})),v||(v=me.createElement("text",{x:150,y:415,className:"step-text"},"Execute Cached")),E||(E=me.createElement("text",{x:150,y:430,className:"step-text"},"Detox Code")),b||(b=me.createElement("rect",{x:50,y:460,width:200,height:50,fill:"#f3e5f5",stroke:"#333",rx:10,ry:10})),w||(w=me.createElement("text",{x:150,y:485,className:"step-text"},"Provide Feedback")),k||(k=me.createElement("text",{x:150,y:500,className:"sub-text"},"(Error / Result)")),N||(N=me.createElement("rect",{x:550,y:320,width:200,height:50,fill:"#ffebee",stroke:"#333",rx:10,ry:10})),C||(C=me.createElement("text",{x:650,y:345,className:"step-text"},"Create LLM Prompt")),O||(O=me.createElement("text",{x:650,y:360,className:"sub-text"},"(Based on Context and Intent)")),D||(D=me.createElement("rect",{x:550,y:390,width:200,height:50,fill:"#d4fffc",stroke:"#333",rx:10,ry:10})),M||(M=me.createElement("text",{x:650,y:415,className:"step-text"},"Generate Detox Code")),P||(P=me.createElement("text",{x:650,y:430,className:"sub-text"},"(Using LLM)")),z||(z=me.createElement("rect",{x:550,y:460,width:200,height:50,fill:"#c8e6c9",stroke:"#333",rx:10,ry:10})),j||(j=me.createElement("text",{x:650,y:485,className:"step-text"},"Execute Generated")),T||(T=me.createElement("text",{x:650,y:500,className:"step-text"},"Detox Code")),L||(L=me.createElement("rect",{x:550,y:530,width:200,height:50,fill:"#fff8e1",stroke:"#333",rx:10,ry:10})),S||(S=me.createElement("text",{x:650,y:560,className:"step-text"},"Cache Generated Code")),I||(I=me.createElement("rect",{x:550,y:600,width:200,height:50,fill:"#f3e5f5",stroke:"#333",rx:10,ry:10})),A||(A=me.createElement("text",{x:650,y:625,className:"step-text"},"Provide Feedback")),R||(R=me.createElement("text",{x:650,y:640,className:"sub-text"},"(Error / Result)")),B||(B=me.createElement("rect",{x:300,y:680,width:200,height:50,fill:"#fff",stroke:"#333",rx:10,ry:10})),G||(G=me.createElement("text",{x:400,y:710,className:"step-text"},"Next Step?")),F||(F=me.createElement("rect",{x:350,y:770,width:100,height:40,fill:"#d1eaff",stroke:"#333",rx:10,ry:10})),U||(U=me.createElement("text",{x:400,y:795,className:"step-text"},"End")),H||(H=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M400 60v20"})),W||(W=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M400 130v30"})),X||(X=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M400 210v20"})),V||(V=me.createElement("path",{stroke:"#333",d:"M300 260H150"})),Y||(Y=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M150 260v60"})),q||(q=me.createElement("text",{x:275,y:250,fontSize:12,fill:"#333"},"Yes")),_||(_=me.createElement("path",{stroke:"#333",d:"M500 260h150"})),J||(J=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M650 260v60"})),K||(K=me.createElement("text",{x:525,y:250,fontSize:12,fill:"#333"},"No")),Q||(Q=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M150 370v20"})),Z||(Z=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M150 440v20"})),$||($=me.createElement("path",{stroke:"#333",d:"M250 490h150"})),ee||(ee=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M400 490v190"})),te||(te=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M650 370v20"})),re||(re=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M650 440v20"})),ae||(ae=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M650 510v20"})),ne||(ne=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M650 580v20"})),oe||(oe=me.createElement("path",{stroke:"#333",d:"M550 630H400"})),ie||(ie=me.createElement("path",{stroke:"#333",markerEnd:"url(#arrowhead)",d:"M400 730v40"})),le||(le=me.createElement("text",{x:410,y:760,fontSize:12,fill:"#333"},"No")),se||(se=me.createElement("path",{d:"M300 710c-50 0-50-620 100-620",fill:"none",stroke:"#333",markerEnd:"url(#arrowhead)"})),ce||(ce=me.createElement("text",{x:260,y:400,fontSize:12,fill:"#333"},"Yes")))},xe={},ye="Technical Overview",fe={unversionedId:"copilot/technical-overview",id:"version-20.x/copilot/technical-overview",title:"Technical Overview",description:"Detox Copilot integrates seamlessly with your testing environment by combining natural language processing with Detox's robust testing capabilities.",source:"@site/versioned_docs/version-20.x/copilot/technical-overview.mdx",sourceDirName:"copilot",slug:"/copilot/technical-overview",permalink:"/Detox/docs/copilot/technical-overview",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/copilot/technical-overview.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Detox Copilot Best Practices",permalink:"/Detox/docs/copilot/best-practices"},next:{title:"Dealing With Problems With Building the App & Detox",permalink:"/Detox/docs/troubleshooting/building-the-app"}},ge={},ve=[{value:"Building Blocks of Detox Copilot",id:"building-blocks-of-detox-copilot",level:2},{value:"Copilot's Execution Flow",id:"copilots-execution-flow",level:2},{value:"Performance Optimization",id:"performance-optimization",level:3}],Ee={toc:ve},be="wrapper";function we(e){let{components:t,...r}=e;return(0,de.yg)(be,(0,pe.A)({},Ee,r,{components:t,mdxType:"MDXLayout"}),(0,de.yg)("h1",{id:"technical-overview"},"Technical Overview"),(0,de.yg)("p",null,"Detox Copilot integrates seamlessly with your testing environment by combining natural language processing with Detox's robust testing capabilities."),(0,de.yg)("h2",{id:"building-blocks-of-detox-copilot"},"Building Blocks of Detox Copilot"),(0,de.yg)("p",null,"To enable Detox Copilot to work harmoniously with Detox and your app, it relies on several building blocks:"),(0,de.yg)("ul",null,(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"Dynamic Code Generation"),": Copilot generates Detox code on-the-fly to perform actions or assertions based on your instructions."),(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"Visual Analysis"),": Copilot can analyze the app's screen to verify the presence of specific elements or text, enabling assertions beyond standard UI checks."),(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"App View Hierarchy"),": Detox generates an XML representation of the app's view hierarchy, helping Copilot interact with all UI elements, even those not directly visible."),(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"Snapshot Images"),": Optional snapshot images provide Copilot with visual context for more precise understanding and analysis."),(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"Injected Test IDs"),": When necessary, Detox injects unique test IDs to ensure reliable access to UI elements."),(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"Caching Mechanism"),": Copilot caches execution results to optimize performance and reduce unnecessary LLM calls (see ",(0,de.yg)("a",{parentName:"li",href:"#performance-optimization"},"Performance Optimization"),")."),(0,de.yg)("li",{parentName:"ul"},(0,de.yg)("strong",{parentName:"li"},"Test Context Awareness"),": Copilot maintains awareness of previously executed steps, ensuring continuity and readability in the test flow.")),(0,de.yg)("h2",{id:"copilots-execution-flow"},"Copilot's Execution Flow"),(0,de.yg)(he,{style:{backgroundColor:"#f5f5f5",borderRadius:"10px"},mdxType:"OverviewSVG"}),(0,de.yg)("p",null,"The execution flow of Detox Copilot can be broken down into six main steps:"),(0,de.yg)("ol",null,(0,de.yg)("li",{parentName:"ol"},(0,de.yg)("strong",{parentName:"li"},"Gather Context"),": Collect relevant app state, view hierarchy, and previous step results."),(0,de.yg)("li",{parentName:"ol"},(0,de.yg)("strong",{parentName:"li"},"Interpret Intent"),": Use the LLM to interpret the natural language instruction."),(0,de.yg)("li",{parentName:"ol"},(0,de.yg)("strong",{parentName:"li"},"Generate Code"),": Create the appropriate Detox commands."),(0,de.yg)("li",{parentName:"ol"},(0,de.yg)("strong",{parentName:"li"},"Execute Action"),": Run the generated Detox code."),(0,de.yg)("li",{parentName:"ol"},(0,de.yg)("strong",{parentName:"li"},"Cache Results"),": Store execution results to optimize future runs."),(0,de.yg)("li",{parentName:"ol"},(0,de.yg)("strong",{parentName:"li"},"Provide Feedback"),": Return values or confirm actions for subsequent steps.")),(0,de.yg)("p",null,"By combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions."),(0,de.yg)("h3",{id:"performance-optimization"},"Performance Optimization"),(0,de.yg)("p",null,"Detox Copilot is designed to avoid unnecessary calls to the LLM service and optimize performance using static cache that is based on the current state of the app.\nThis minimizes the number of calls to the LLM service and reduces latency.\nHowever, you can optimize your ",(0,de.yg)("inlineCode",{parentName:"p"},"PromptHandler")," implementation to reduce latency and improve response times (e.g., by reducing the image size or implementing a server-side cache).\nWe have plans to optimize even further by introducing more advanced caching mechanisms for better performance."))}we.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/52259249.8e1eb1ca.js b/assets/js/52259249.8e1eb1ca.js new file mode 100644 index 0000000000..456f008f45 --- /dev/null +++ b/assets/js/52259249.8e1eb1ca.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[5193],{15680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>y});var a=n(96540);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(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||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}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)},g="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),g=p(n),m=r,y=g["".concat(s,".").concat(m)]||g[m]||u[m]||i;return n?a.createElement(y,o(o({ref:t},c),{},{components:n})):a.createElement(y,o({ref:t},c))}));function y(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[g]="string"==typeof e?e:r,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var a=n(58168),r=(n(96540),n(15680));const i={},o="Detox Copilot Best Practices",l={unversionedId:"copilot/best-practices",id:"version-20.x/copilot/best-practices",title:"Detox Copilot Best Practices",description:"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app. In case you're wondering how to make the most out of this feature, here are some best practices to follow when writing your Copilot intents.",source:"@site/versioned_docs/version-20.x/copilot/best-practices.md",sourceDirName:"copilot",slug:"/copilot/best-practices",permalink:"/Detox/docs/copilot/best-practices",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/copilot/best-practices.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Natural Language Testing with Detox Copilot",permalink:"/Detox/docs/copilot/testing-with-copilot"},next:{title:"Technical Overview",permalink:"/Detox/docs/copilot/technical-overview"}},s={},p=[{value:"Step-by-Step Instructions",id:"step-by-step-instructions",level:2},{value:"Be Specific and Clear",id:"be-specific-and-clear",level:2},{value:"One Action per Step",id:"one-action-per-step",level:2},{value:"Use Exact Labels",id:"use-exact-labels",level:2},{value:"Keep Assertions Simple",id:"keep-assertions-simple",level:2},{value:"Leverage Visual Context",id:"leverage-visual-context",level:2},{value:"Avoid Ambiguity",id:"avoid-ambiguity",level:2},{value:"General Recommendations",id:"general-recommendations",level:2}],c={toc:p},g="wrapper";function u(e){let{components:t,...n}=e;return(0,r.yg)(g,(0,a.A)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"detox-copilot-best-practices"},"Detox Copilot Best Practices"),(0,r.yg)("p",null,"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app. In case you're wondering how to make the most out of this feature, here are some best practices to follow when writing your Copilot intents."),(0,r.yg)("h2",{id:"step-by-step-instructions"},"Step-by-Step Instructions"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Write Sequential Steps"),": Describe your test steps in a clear and sequential manner."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Example"),":")),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should navigate and add a product to the cart', async () => {\n await copilot.perform(\n 'Navigate to the \"Products\" page',\n 'Tap on the \"Add to Cart\" button for the first product',\n 'Verify that the \"Added to Cart\" pop-up is displayed'\n );\n});\n")),(0,r.yg)("h2",{id:"be-specific-and-clear"},"Be Specific and Clear"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Provide Clear Instructions"),": The clearer your instructions, the better Copilot can interpret them."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Example"),":",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Good"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Tap on the \"Login\" button'")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Better"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Tap on the \"Login\" button located at the top right corner'"))))),(0,r.yg)("h2",{id:"one-action-per-step"},"One Action per Step"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},(0,r.yg)("strong",{parentName:"p"},"Avoid Combining Multiple Actions"),": Keep each step focused on a single action or assertion.")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("p",{parentName:"li"},(0,r.yg)("strong",{parentName:"p"},"Example"),":"),(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Avoid"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Tap on the \"Login\" button and enter credentials'")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Prefer"),":")),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-javascript"},'\'Tap on the "Login" button\',\n\'Enter "user@example.com" into the "Email" field\'\n')))),(0,r.yg)("h2",{id:"use-exact-labels"},"Use Exact Labels"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Refer to UI Elements Precisely"),": Use the exact text or identifiers as they appear in the app."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Example"),":",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Good"),": ",(0,r.yg)("inlineCode",{parentName:"li"},'\'Enter "password123" into the "Password" field\'')),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Avoid"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Enter password into its field'"))))),(0,r.yg)("h2",{id:"keep-assertions-simple"},"Keep Assertions Simple"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Focus on Specific Outcomes"),": Make assertions straightforward and specific."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Example"),":",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Good"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Verify that the \"Welcome\" message is displayed'")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Avoid"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Check if the welcome message appears correctly on the screen'"))))),(0,r.yg)("h2",{id:"leverage-visual-context"},"Leverage Visual Context"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Utilize Visual Descriptions"),": If your LLM supports image snapshots, include visual context in your intents."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Example"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Ensure the profile picture is visible at the top of the screen'"))),(0,r.yg)("h2",{id:"avoid-ambiguity"},"Avoid Ambiguity"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Specify Elements Precisely"),": If multiple elements could match, provide additional details."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Example"),":",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Ambiguous"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Tap on the \"Submit\" button'")),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Specific"),": ",(0,r.yg)("inlineCode",{parentName:"li"},"'Tap on the \"Submit\" button in the registration form'"))))),(0,r.yg)("h2",{id:"general-recommendations"},"General Recommendations"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Flexibility"),": While it's best to provide clear instructions, Copilot is designed to interpret a variety of phrasing. Different approaches can work, and you are encouraged to experiment."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Feedback Loop"),": Observe how Copilot interprets your instructions and adjust accordingly."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("strong",{parentName:"li"},"Model Selection"),": Choose an LLM model that best suits your application's complexity and language requirements. We recommend advanced models like ",(0,r.yg)("strong",{parentName:"li"},"Sonnet 3.5")," or ",(0,r.yg)("strong",{parentName:"li"},"GPT-4o")," for better performance.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5eff386f.ed6cb595.js b/assets/js/5eff386f.ed6cb595.js deleted file mode 100644 index f0211d9c85..0000000000 --- a/assets/js/5eff386f.ed6cb595.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[9592],{15680:(e,n,t)=>{t.d(n,{xA:()=>p,yg:()=>m});var i=t(96540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,i)}return t}function a(e){for(var n=1;n=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var s=i.createContext({}),d=function(e){var n=i.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):a(a({},n),e)),t},p=function(e){var n=d(e.components);return i.createElement(s.Provider,{value:n},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},g=i.forwardRef((function(e,n){var t=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),c=d(t),g=o,m=c["".concat(s,".").concat(g)]||c[g]||u[g]||r;return t?i.createElement(m,a(a({ref:n},p),{},{components:t})):i.createElement(m,a({ref:n},p))}));function m(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var r=t.length,a=new Array(r);a[0]=g;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[c]="string"==typeof e?e:o,a[1]=l;for(var d=2;d{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var i=t(58168),o=(t(96540),t(15680));const r={},a="Dealing With Problems With Building the App & Detox",l={unversionedId:"troubleshooting/building-the-app",id:"version-20.x/troubleshooting/building-the-app",title:"Dealing With Problems With Building the App & Detox",description:"This page is about issues related to building the app, typically triggered when running detox build (and not detox test, for example).",source:"@site/versioned_docs/version-20.x/troubleshooting/building-the-app.md",sourceDirName:"troubleshooting",slug:"/troubleshooting/building-the-app",permalink:"/Detox/docs/troubleshooting/building-the-app",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/troubleshooting/building-the-app.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Uninstalling Detox",permalink:"/Detox/docs/guide/uninstalling"},next:{title:"Dealing With Problems With Running Tests",permalink:"/Detox/docs/troubleshooting/running-tests"}},s={},d=[{value:"Android",id:"android",level:2},{value:"Problem: AAPT - resource linking failure",id:"problem-aapt---resource-linking-failure",level:3},{value:"Problem: minSdkVersion mismatch",id:"problem-minsdkversion-mismatch",level:3},{value:"Problem: Kotlin stdlib version conflicts",id:"problem-kotlin-stdlib-version-conflicts",level:3},{value:"Resolving for a precompiled dependency (.aar)",id:"resolving-for-a-precompiled-dependency-aar",level:4},{value:"Resolving for a compiling subproject",id:"resolving-for-a-compiling-subproject",level:4},{value:"Problem: Duplicate files copied in ...",id:"problem-duplicate-files-copied-in-",level:3}],p={toc:d},c="wrapper";function u(e){let{components:n,...t}=e;return(0,o.yg)(c,(0,i.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("h1",{id:"dealing-with-problems-with-building-the-app--detox"},"Dealing With Problems With Building the App & Detox"),(0,o.yg)("p",null,"This page is about issues related to building the app, typically triggered when running ",(0,o.yg)("inlineCode",{parentName:"p"},"detox build")," (and not ",(0,o.yg)("inlineCode",{parentName:"p"},"detox test"),", for example)."),(0,o.yg)("h2",{id:"android"},"Android"),(0,o.yg)("h3",{id:"problem-aapt---resource-linking-failure"},"Problem: AAPT - resource linking failure"),(0,o.yg)("p",null,"For build errors involving AAPT resource linking failure, such as this one:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"Execution failed for task ':app:processReleaseAndroidTestResources'.\n> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction\n > Android resource linking failed\n ERROR:: AAPT: error: resource style/Widget.AppCompat.TextView ...\n")),(0,o.yg)("p",null,"Ensure that the following line appears in your app build script in the ",(0,o.yg)("inlineCode",{parentName:"p"},"dependencies")," section:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/app/build.gradle"',title:'"android/app/build.gradle"'},"dependencies {\n // ...\n implementation 'androidx.appcompat:appcompat:1.1.0' // (check what the latest version is!)\n}\n")),(0,o.yg)("h3",{id:"problem-minsdkversion-mismatch"},"Problem: minSdkVersion mismatch"),(0,o.yg)("p",null,"For Gradle errors involving ",(0,o.yg)("inlineCode",{parentName:"p"},"minSdkVersion")," mismatches resembling this one:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-text"},'uses-sdk:minSdkVersion 18 cannot be smaller than version 21 declared in library [com.facebook.react:react-native:0.64.3] /Users/janedoe/.gradle/caches/transforms-3/6a9cd4eeeb285f80b9e6f413ecd78d0d/transformed/jetified-react-native-0.64.3/AndroidManifest.xml as the library might be using APIs not available in 18\n Suggestion: use a compatible library with a minSdk of at most 18,\n or increase this project\'s minSdk version to at least 21,\n or use tools:overrideLibrary="com.facebook.react" to force usage (may lead to runtime failures)\n')),(0,o.yg)("p",null,"Try applying the solution suggested in ",(0,o.yg)("a",{parentName:"p",href:"https://stackoverflow.com/questions/21032271/how-to-inject-android-configuration-to-each-subproject-with-gradle"},"this Stack-overflow")," post, namely adding this to your root-project's ",(0,o.yg)("inlineCode",{parentName:"p"},"build.gradle")," file (replace ",(0,o.yg)("inlineCode",{parentName:"p"},"21")," those matching your app's ",(0,o.yg)("inlineCode",{parentName:"p"},"build.gradle"),"):"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/build.gradle"',title:'"android/build.gradle"'},"allprojects {\n afterEvaluate {\n if (it.hasProperty('android')){\n android {\n defaultConfig {\n minSdkVersion 21 // Replace '21' with whatever suites your case\n }\n }\n }\n }\n}\n")),(0,o.yg)("h3",{id:"problem-kotlin-stdlib-version-conflicts"},"Problem: Kotlin ",(0,o.yg)("inlineCode",{parentName:"h3"},"stdlib")," version conflicts"),(0,o.yg)("p",null,"The problems and resolutions here are different depending on whether you\u2019re using Detox as a precompiled dependency artifact (i.e. an ",(0,o.yg)("inlineCode",{parentName:"p"},".aar"),") - which is by far the common case, or compiling it yourself."),(0,o.yg)("h4",{id:"resolving-for-a-precompiled-dependency-aar"},"Resolving for a precompiled dependency (",(0,o.yg)("inlineCode",{parentName:"h4"},".aar"),")"),(0,o.yg)("p",null,"Of all ",(0,o.yg)("a",{parentName:"p",href:"https://kotlinlang.org/docs/reference/using-gradle.html#configuring-dependencies"},"Kotlin implementation flavors"),", Detox assumes the most recent one, namely ",(0,o.yg)("inlineCode",{parentName:"p"},"kotlin-stdlib-jdk8"),'. If your Android build fails due to conflicts with implementations coming from other dependencies or even your own app, consider adding an exclusion to either the "other" dependencies or detox itself, for example:'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-diff"},"dependencies {\n- androidTestImplementation('com.wix:detox:+')\n+ androidTestImplementation('com.wix:detox:+') {\n+ exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'\n+ }\n}\n")),(0,o.yg)("p",null,"Detox should work with ",(0,o.yg)("inlineCode",{parentName:"p"},"kotlin-stdlib-jdk7"),", as well."),(0,o.yg)("p",null,"A typical error output formed by ",(0,o.yg)("inlineCode",{parentName:"p"},"Gradle")," in this case is as provided, for example, in ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/1380"},"#1380"),":"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"Could not determine the dependencies of task ':detox:compileDebugAidl'.\n> Could not resolve all task dependencies for configuration ':detox:debugCompileClasspath'.\n > Could not resolve org.jetbrains.kotlin:kotlin-stdlib:1.3.0.\n Required by:\n project :detox\n > Cannot find a version of 'org.jetbrains.kotlin:kotlin-stdlib' that satisfies the version constraints:\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.squareup.okio:okio:2.2.2' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.0' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.0'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.facebook.react:react-native:0.59.5' --\x3e 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.facebook.react:react-native:0.59.5' --\x3e 'com.squareup.okio:okio:2.2.2' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.0' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.0' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.0'\n Constraint path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0\n Constraint path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0\n\n > Could not resolve org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0.\n Required by:\n project :detox\n > Cannot find a version of 'org.jetbrains.kotlin:kotlin-stdlib-common' that satisfies the version constraints:\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.30'\n Constraint path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-common' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0\n")),(0,o.yg)("p",null,"(i.e. the project indirectly depends on different versions of ",(0,o.yg)("inlineCode",{parentName:"p"},"kotlin-stdlib"),", such as ",(0,o.yg)("inlineCode",{parentName:"p"},"1.3.0"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"1.3.30"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"1.2.60"),")"),(0,o.yg)("h4",{id:"resolving-for-a-compiling-subproject"},"Resolving for a compiling subproject"),(0,o.yg)("p",null,"Detox requires the Kotlin standard-library as its own dependency. Due to the ",(0,o.yg)("a",{parentName:"p",href:"https://kotlinlang.org/docs/reference/using-gradle.html#configuring-dependencies"},"many flavors")," by which Kotlin has been released, multiple dependencies often create a conflict."),(0,o.yg)("p",null,"For that, Detox allows for the exact specification of the standard library to use using two Gradle globals: ",(0,o.yg)("inlineCode",{parentName:"p"},"detoxKotlinVersion")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"detoxKotlinStdlib"),". You can define both in your root build script file:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/build.gradle"',title:'"android/build.gradle"'},"buildscript {\n // ...\n ext.detoxKotlinVersion = '1.3.0' // Detox' default is 1.2.0\n ext.detoxKotlinStdlib = 'kotlin-stdlib-jdk7' // Detox' default is kotlin-stdlib-jdk8\n}\n")),(0,o.yg)("h3",{id:"problem-duplicate-files-copied-in-"},"Problem: ",(0,o.yg)("inlineCode",{parentName:"h3"},"Duplicate files copied in ...")),(0,o.yg)("p",null,"If you get an error like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.\n> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/LICENSE\n")),(0,o.yg)("p",null,"You need to add this to the ",(0,o.yg)("inlineCode",{parentName:"p"},"android")," section of your app build script:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/app/build.gradle"',title:'"android/app/build.gradle"'},"packagingOptions {\n exclude 'META-INF/LICENSE'\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5eff386f.ef7782e7.js b/assets/js/5eff386f.ef7782e7.js new file mode 100644 index 0000000000..ff923b4a7f --- /dev/null +++ b/assets/js/5eff386f.ef7782e7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[9592],{15680:(e,n,t)=>{t.d(n,{xA:()=>p,yg:()=>m});var i=t(96540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,i)}return t}function a(e){for(var n=1;n=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var s=i.createContext({}),d=function(e){var n=i.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):a(a({},n),e)),t},p=function(e){var n=d(e.components);return i.createElement(s.Provider,{value:n},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},g=i.forwardRef((function(e,n){var t=e.components,o=e.mdxType,r=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),c=d(t),g=o,m=c["".concat(s,".").concat(g)]||c[g]||u[g]||r;return t?i.createElement(m,a(a({ref:n},p),{},{components:t})):i.createElement(m,a({ref:n},p))}));function m(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var r=t.length,a=new Array(r);a[0]=g;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[c]="string"==typeof e?e:o,a[1]=l;for(var d=2;d{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>a,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var i=t(58168),o=(t(96540),t(15680));const r={},a="Dealing With Problems With Building the App & Detox",l={unversionedId:"troubleshooting/building-the-app",id:"version-20.x/troubleshooting/building-the-app",title:"Dealing With Problems With Building the App & Detox",description:"This page is about issues related to building the app, typically triggered when running detox build (and not detox test, for example).",source:"@site/versioned_docs/version-20.x/troubleshooting/building-the-app.md",sourceDirName:"troubleshooting",slug:"/troubleshooting/building-the-app",permalink:"/Detox/docs/troubleshooting/building-the-app",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/troubleshooting/building-the-app.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Technical Overview",permalink:"/Detox/docs/copilot/technical-overview"},next:{title:"Dealing With Problems With Running Tests",permalink:"/Detox/docs/troubleshooting/running-tests"}},s={},d=[{value:"Android",id:"android",level:2},{value:"Problem: AAPT - resource linking failure",id:"problem-aapt---resource-linking-failure",level:3},{value:"Problem: minSdkVersion mismatch",id:"problem-minsdkversion-mismatch",level:3},{value:"Problem: Kotlin stdlib version conflicts",id:"problem-kotlin-stdlib-version-conflicts",level:3},{value:"Resolving for a precompiled dependency (.aar)",id:"resolving-for-a-precompiled-dependency-aar",level:4},{value:"Resolving for a compiling subproject",id:"resolving-for-a-compiling-subproject",level:4},{value:"Problem: Duplicate files copied in ...",id:"problem-duplicate-files-copied-in-",level:3}],p={toc:d},c="wrapper";function u(e){let{components:n,...t}=e;return(0,o.yg)(c,(0,i.A)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("h1",{id:"dealing-with-problems-with-building-the-app--detox"},"Dealing With Problems With Building the App & Detox"),(0,o.yg)("p",null,"This page is about issues related to building the app, typically triggered when running ",(0,o.yg)("inlineCode",{parentName:"p"},"detox build")," (and not ",(0,o.yg)("inlineCode",{parentName:"p"},"detox test"),", for example)."),(0,o.yg)("h2",{id:"android"},"Android"),(0,o.yg)("h3",{id:"problem-aapt---resource-linking-failure"},"Problem: AAPT - resource linking failure"),(0,o.yg)("p",null,"For build errors involving AAPT resource linking failure, such as this one:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"Execution failed for task ':app:processReleaseAndroidTestResources'.\n> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction\n > Android resource linking failed\n ERROR:: AAPT: error: resource style/Widget.AppCompat.TextView ...\n")),(0,o.yg)("p",null,"Ensure that the following line appears in your app build script in the ",(0,o.yg)("inlineCode",{parentName:"p"},"dependencies")," section:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/app/build.gradle"',title:'"android/app/build.gradle"'},"dependencies {\n // ...\n implementation 'androidx.appcompat:appcompat:1.1.0' // (check what the latest version is!)\n}\n")),(0,o.yg)("h3",{id:"problem-minsdkversion-mismatch"},"Problem: minSdkVersion mismatch"),(0,o.yg)("p",null,"For Gradle errors involving ",(0,o.yg)("inlineCode",{parentName:"p"},"minSdkVersion")," mismatches resembling this one:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-text"},'uses-sdk:minSdkVersion 18 cannot be smaller than version 21 declared in library [com.facebook.react:react-native:0.64.3] /Users/janedoe/.gradle/caches/transforms-3/6a9cd4eeeb285f80b9e6f413ecd78d0d/transformed/jetified-react-native-0.64.3/AndroidManifest.xml as the library might be using APIs not available in 18\n Suggestion: use a compatible library with a minSdk of at most 18,\n or increase this project\'s minSdk version to at least 21,\n or use tools:overrideLibrary="com.facebook.react" to force usage (may lead to runtime failures)\n')),(0,o.yg)("p",null,"Try applying the solution suggested in ",(0,o.yg)("a",{parentName:"p",href:"https://stackoverflow.com/questions/21032271/how-to-inject-android-configuration-to-each-subproject-with-gradle"},"this Stack-overflow")," post, namely adding this to your root-project's ",(0,o.yg)("inlineCode",{parentName:"p"},"build.gradle")," file (replace ",(0,o.yg)("inlineCode",{parentName:"p"},"21")," those matching your app's ",(0,o.yg)("inlineCode",{parentName:"p"},"build.gradle"),"):"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/build.gradle"',title:'"android/build.gradle"'},"allprojects {\n afterEvaluate {\n if (it.hasProperty('android')){\n android {\n defaultConfig {\n minSdkVersion 21 // Replace '21' with whatever suites your case\n }\n }\n }\n }\n}\n")),(0,o.yg)("h3",{id:"problem-kotlin-stdlib-version-conflicts"},"Problem: Kotlin ",(0,o.yg)("inlineCode",{parentName:"h3"},"stdlib")," version conflicts"),(0,o.yg)("p",null,"The problems and resolutions here are different depending on whether you\u2019re using Detox as a precompiled dependency artifact (i.e. an ",(0,o.yg)("inlineCode",{parentName:"p"},".aar"),") - which is by far the common case, or compiling it yourself."),(0,o.yg)("h4",{id:"resolving-for-a-precompiled-dependency-aar"},"Resolving for a precompiled dependency (",(0,o.yg)("inlineCode",{parentName:"h4"},".aar"),")"),(0,o.yg)("p",null,"Of all ",(0,o.yg)("a",{parentName:"p",href:"https://kotlinlang.org/docs/reference/using-gradle.html#configuring-dependencies"},"Kotlin implementation flavors"),", Detox assumes the most recent one, namely ",(0,o.yg)("inlineCode",{parentName:"p"},"kotlin-stdlib-jdk8"),'. If your Android build fails due to conflicts with implementations coming from other dependencies or even your own app, consider adding an exclusion to either the "other" dependencies or detox itself, for example:'),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-diff"},"dependencies {\n- androidTestImplementation('com.wix:detox:+')\n+ androidTestImplementation('com.wix:detox:+') {\n+ exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'\n+ }\n}\n")),(0,o.yg)("p",null,"Detox should work with ",(0,o.yg)("inlineCode",{parentName:"p"},"kotlin-stdlib-jdk7"),", as well."),(0,o.yg)("p",null,"A typical error output formed by ",(0,o.yg)("inlineCode",{parentName:"p"},"Gradle")," in this case is as provided, for example, in ",(0,o.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/1380"},"#1380"),":"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"Could not determine the dependencies of task ':detox:compileDebugAidl'.\n> Could not resolve all task dependencies for configuration ':detox:debugCompileClasspath'.\n > Could not resolve org.jetbrains.kotlin:kotlin-stdlib:1.3.0.\n Required by:\n project :detox\n > Cannot find a version of 'org.jetbrains.kotlin:kotlin-stdlib' that satisfies the version constraints:\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.squareup.okio:okio:2.2.2' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.0' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.0'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.facebook.react:react-native:0.59.5' --\x3e 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.facebook.react:react-native:0.59.5' --\x3e 'com.squareup.okio:okio:2.2.2' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'\n Dependency path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.0' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.0' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.0'\n Constraint path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0\n Constraint path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0\n\n > Could not resolve org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0.\n Required by:\n project :detox\n > Cannot find a version of 'org.jetbrains.kotlin:kotlin-stdlib-common' that satisfies the version constraints:\n Dependency path 'OurApp:detox:unspecified' --\x3e 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.30'\n Constraint path 'OurApp:detox:unspecified' --\x3e 'org.jetbrains.kotlin:kotlin-stdlib-common' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0\n")),(0,o.yg)("p",null,"(i.e. the project indirectly depends on different versions of ",(0,o.yg)("inlineCode",{parentName:"p"},"kotlin-stdlib"),", such as ",(0,o.yg)("inlineCode",{parentName:"p"},"1.3.0"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"1.3.30"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"1.2.60"),")"),(0,o.yg)("h4",{id:"resolving-for-a-compiling-subproject"},"Resolving for a compiling subproject"),(0,o.yg)("p",null,"Detox requires the Kotlin standard-library as its own dependency. Due to the ",(0,o.yg)("a",{parentName:"p",href:"https://kotlinlang.org/docs/reference/using-gradle.html#configuring-dependencies"},"many flavors")," by which Kotlin has been released, multiple dependencies often create a conflict."),(0,o.yg)("p",null,"For that, Detox allows for the exact specification of the standard library to use using two Gradle globals: ",(0,o.yg)("inlineCode",{parentName:"p"},"detoxKotlinVersion")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"detoxKotlinStdlib"),". You can define both in your root build script file:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/build.gradle"',title:'"android/build.gradle"'},"buildscript {\n // ...\n ext.detoxKotlinVersion = '1.3.0' // Detox' default is 1.2.0\n ext.detoxKotlinStdlib = 'kotlin-stdlib-jdk7' // Detox' default is kotlin-stdlib-jdk8\n}\n")),(0,o.yg)("h3",{id:"problem-duplicate-files-copied-in-"},"Problem: ",(0,o.yg)("inlineCode",{parentName:"h3"},"Duplicate files copied in ...")),(0,o.yg)("p",null,"If you get an error like this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.\n> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/LICENSE\n")),(0,o.yg)("p",null,"You need to add this to the ",(0,o.yg)("inlineCode",{parentName:"p"},"android")," section of your app build script:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-gradle",metastring:'title="android/app/build.gradle"',title:'"android/app/build.gradle"'},"packagingOptions {\n exclude 'META-INF/LICENSE'\n}\n")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/63371bf1.56f4aa99.js b/assets/js/63371bf1.a72526cb.js similarity index 65% rename from assets/js/63371bf1.56f4aa99.js rename to assets/js/63371bf1.a72526cb.js index 27b79b4775..8356a3e561 100644 --- a/assets/js/63371bf1.56f4aa99.js +++ b/assets/js/63371bf1.a72526cb.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[9118],{15680:(e,t,r)=>{r.d(t,{xA:()=>u,yg:()=>m});var o=r(96540);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,o)}return r}function a(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var l=o.createContext({}),c=function(e){var t=o.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):a(a({},t),e)),r},u=function(e){var t=c(e.components);return o.createElement(l.Provider,{value:t},e.children)},p="mdxType",g={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},d=o.forwardRef((function(e,t){var r=e.components,n=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),p=c(r),d=n,m=p["".concat(l,".").concat(d)]||p[d]||g[d]||i;return r?o.createElement(m,a(a({ref:t},u),{},{components:r})):o.createElement(m,a({ref:t},u))}));function m(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=r.length,a=new Array(i);a[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:n,a[1]=s;for(var c=2;c{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>g,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var o=r(58168),n=(r(96540),r(15680));const i={sidebar_label:"Overview"},a="Code Changes Overview",s={unversionedId:"contributing/code/overview",id:"version-20.x/contributing/code/overview",title:"Code Changes Overview",description:"Welcome to the code changes section! As a contributor, it's essential to understand the project's goals and adhere to its code of conduct. Before contributing, please review any existing issues related to your work, ensure your code is well-documented, and has adequate test coverage. It's also important that your code is compatible with the project's supported platforms and their versions.",source:"@site/versioned_docs/version-20.x/contributing/code/overview.md",sourceDirName:"contributing/code",slug:"/contributing/code/overview",permalink:"/Detox/docs/contributing/code/overview",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/contributing/code/overview.md",tags:[],version:"20.x",frontMatter:{sidebar_label:"Overview"},sidebar:"contributeSidebar",previous:{title:"Feedback and Suggestions",permalink:"/Detox/docs/contributing/feature-requests"},next:{title:"Setting up the Development Environment",permalink:"/Detox/docs/contributing/code/setting-up-the-dev-environment"}},l={},c=[{value:"Repository Structure",id:"repository-structure",level:2}],u={toc:c},p="wrapper";function g(e){let{components:t,...r}=e;return(0,n.yg)(p,(0,o.A)({},u,r,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("h1",{id:"code-changes-overview"},"Code Changes Overview"),(0,n.yg)("p",null,"Welcome to the code changes section! As a contributor, it's essential to understand the project's goals and adhere to its code of conduct. Before contributing, please review any existing issues related to your work, ensure your code is well-documented, and has adequate test coverage. It's also important that your code is compatible with the project's supported platforms and their versions."),(0,n.yg)("p",null,"Our collaborative workflow is simple:"),(0,n.yg)("ol",null,(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Identify an Issue:")," If not exists already, create an issue for new features or bug reports, outlining your proposal or the identified problem."),(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Propose a Solution:")," Open a pull request with a proposed solution to the issue. On complex issues, it's recommended to discuss your approach with the community and maintainers before submitting a PR."),(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Engage in Review:")," A collaborator will review your pull request. Reviews from other contributors are also encouraged."),(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Merge and Release:")," After the review, a collaborator will merge your contribution, typically releasing it in the next version of the project.")),(0,n.yg)("p",null,"We use ",(0,n.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox"},"GitHub")," for managing pull requests, conducting code reviews, and tracking issues."),(0,n.yg)("p",null,"The code review process is central to our collaboration. Every contribution must go through a review before merging to maintain the quality of our codebase. As a contributor, being willing to discuss your work, respond to feedback, and work with the community is key to improving the project and creating a positive environment for all contributors."),(0,n.yg)("h2",{id:"repository-structure"},"Repository Structure"),(0,n.yg)("p",null,"Our GitHub repository is a monorepo, which means it contains multiple Detox-related projects and packages."),(0,n.yg)("p",null,"The main package is the Detox framework, which is the core of the project.\nIt contains the native code for iOS and Android, as well as the JavaScript code.\nThe other projects are the Detox CLI, the Detox test app, example apps, and the Detox documentation website."),(0,n.yg)("p",null,"Here's a high-level overview of the repository structure:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"detox-cli")," - The CLI for Detox (e.g., ",(0,n.yg)("inlineCode",{parentName:"li"},"detox init"),", ",(0,n.yg)("inlineCode",{parentName:"li"},"detox test"),", read more about our ",(0,n.yg)("a",{parentName:"li",href:"/Detox/docs/cli/overview"},"CLI docs"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"detox-copilot")," - Detox plugin that leverages large language models (LLM) to seamlessly invoke Detox actions (",(0,n.yg)("strong",{parentName:"li"},"work in progress"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"detox")," - The Detox framework",(0,n.yg)("ul",{parentName:"li"},(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"android")," - The Android native code, alongside native unit tests"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"ios")," - The iOS native code, including its native submodules (e.g., DetoxSync)"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"test")," - The Detox self-test app: A full-feature React Native app for end-to-end testing Detox itself",(0,n.yg)("ul",{parentName:"li"},(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"src")," - The app's JavaScript code"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"e2e")," - The Detox self-tests"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"integration")," - Detox integration self-tests"))),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"local-cli")," - Local CLI commands for Detox development (e.g., ",(0,n.yg)("inlineCode",{parentName:"li"},"detox rebuild-framework-cache"),", which rebuilds the iOS framework)"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"runners")," - The Detox runners, which are used to run the tests"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"scripts")," - Scripts for building the framework for publishing"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"src")," - The JavaScript source code of Detox. The include bundled JavaScript unit tests"))),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"website")," - The documentation website of Detox (read more about our ",(0,n.yg)("a",{parentName:"li",href:"/Detox/docs/contributing/documentation"},"documentation site docs"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"docs")," - The documentation of Detox, written in Markdown and published on the website"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"examples")," - Example apps for Detox (for more information, check the ",(0,n.yg)("a",{parentName:"li",href:"/Detox/docs/contributing/code/example-projects"},"list of example projects"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"scripts")," - Scripts for building and testing Detox")))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[9118],{15680:(e,t,r)=>{r.d(t,{xA:()=>u,yg:()=>m});var o=r(96540);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,o)}return r}function a(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var l=o.createContext({}),c=function(e){var t=o.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):a(a({},t),e)),r},u=function(e){var t=c(e.components);return o.createElement(l.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},g=o.forwardRef((function(e,t){var r=e.components,n=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),p=c(r),g=n,m=p["".concat(l,".").concat(g)]||p[g]||d[g]||i;return r?o.createElement(m,a(a({ref:t},u),{},{components:r})):o.createElement(m,a({ref:t},u))}));function m(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=r.length,a=new Array(i);a[0]=g;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[p]="string"==typeof e?e:n,a[1]=s;for(var c=2;c{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var o=r(58168),n=(r(96540),r(15680));const i={sidebar_label:"Overview"},a="Code Changes Overview",s={unversionedId:"contributing/code/overview",id:"version-20.x/contributing/code/overview",title:"Code Changes Overview",description:"Welcome to the code changes section! As a contributor, it's essential to understand the project's goals and adhere to its code of conduct. Before contributing, please review any existing issues related to your work, ensure your code is well-documented, and has adequate test coverage. It's also important that your code is compatible with the project's supported platforms and their versions.",source:"@site/versioned_docs/version-20.x/contributing/code/overview.md",sourceDirName:"contributing/code",slug:"/contributing/code/overview",permalink:"/Detox/docs/contributing/code/overview",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/contributing/code/overview.md",tags:[],version:"20.x",frontMatter:{sidebar_label:"Overview"},sidebar:"contributeSidebar",previous:{title:"Feedback and Suggestions",permalink:"/Detox/docs/contributing/feature-requests"},next:{title:"Setting up the Development Environment",permalink:"/Detox/docs/contributing/code/setting-up-the-dev-environment"}},l={},c=[{value:"Repository Structure",id:"repository-structure",level:2}],u={toc:c},p="wrapper";function d(e){let{components:t,...r}=e;return(0,n.yg)(p,(0,o.A)({},u,r,{components:t,mdxType:"MDXLayout"}),(0,n.yg)("h1",{id:"code-changes-overview"},"Code Changes Overview"),(0,n.yg)("p",null,"Welcome to the code changes section! As a contributor, it's essential to understand the project's goals and adhere to its code of conduct. Before contributing, please review any existing issues related to your work, ensure your code is well-documented, and has adequate test coverage. It's also important that your code is compatible with the project's supported platforms and their versions."),(0,n.yg)("p",null,"Our collaborative workflow is simple:"),(0,n.yg)("ol",null,(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Identify an Issue:")," If not exists already, create an issue for new features or bug reports, outlining your proposal or the identified problem."),(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Propose a Solution:")," Open a pull request with a proposed solution to the issue. On complex issues, it's recommended to discuss your approach with the community and maintainers before submitting a PR."),(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Engage in Review:")," A collaborator will review your pull request. Reviews from other contributors are also encouraged."),(0,n.yg)("li",{parentName:"ol"},(0,n.yg)("strong",{parentName:"li"},"Merge and Release:")," After the review, a collaborator will merge your contribution, typically releasing it in the next version of the project.")),(0,n.yg)("p",null,"We use ",(0,n.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox"},"GitHub")," for managing pull requests, conducting code reviews, and tracking issues."),(0,n.yg)("p",null,"The code review process is central to our collaboration. Every contribution must go through a review before merging to maintain the quality of our codebase. As a contributor, being willing to discuss your work, respond to feedback, and work with the community is key to improving the project and creating a positive environment for all contributors."),(0,n.yg)("h2",{id:"repository-structure"},"Repository Structure"),(0,n.yg)("p",null,"Our GitHub repository is a monorepo, which means it contains multiple Detox-related projects and packages."),(0,n.yg)("p",null,"The main package is the Detox framework, which is the core of the project.\nIt contains the native code for iOS and Android, as well as the JavaScript code.\nThe other projects are the Detox CLI, the Detox test app, example apps, and the Detox documentation website."),(0,n.yg)("p",null,"Here's a high-level overview of the repository structure:"),(0,n.yg)("ul",null,(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"detox-cli")," - The CLI for Detox (e.g., ",(0,n.yg)("inlineCode",{parentName:"li"},"detox init"),", ",(0,n.yg)("inlineCode",{parentName:"li"},"detox test"),", read more about our ",(0,n.yg)("a",{parentName:"li",href:"/Detox/docs/cli/overview"},"CLI docs"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"detox")," - The Detox framework",(0,n.yg)("ul",{parentName:"li"},(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"android")," - The Android native code, alongside native unit tests"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"ios")," - The iOS native code, including its native submodules (e.g., DetoxSync)"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"test")," - The Detox self-test app: A full-feature React Native app for end-to-end testing Detox itself",(0,n.yg)("ul",{parentName:"li"},(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"src")," - The app's JavaScript code"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"e2e")," - The Detox self-tests"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"integration")," - Detox integration self-tests"))),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"local-cli")," - Local CLI commands for Detox development (e.g., ",(0,n.yg)("inlineCode",{parentName:"li"},"detox rebuild-framework-cache"),", which rebuilds the iOS framework)"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"runners")," - The Detox runners, which are used to run the tests"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"scripts")," - Scripts for building the framework for publishing"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"src")," - The JavaScript source code of Detox. The include bundled JavaScript unit tests"))),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"website")," - The documentation website of Detox (read more about our ",(0,n.yg)("a",{parentName:"li",href:"/Detox/docs/contributing/documentation"},"documentation site docs"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"docs")," - The documentation of Detox, written in Markdown and published on the website"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"examples")," - Example apps for Detox (for more information, check the ",(0,n.yg)("a",{parentName:"li",href:"/Detox/docs/contributing/code/example-projects"},"list of example projects"),")"),(0,n.yg)("li",{parentName:"ul"},"\ud83d\udcc1 ",(0,n.yg)("strong",{parentName:"li"},"scripts")," - Scripts for building and testing Detox")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/73f08e0f.ebb577a4.js b/assets/js/73f08e0f.ebb577a4.js deleted file mode 100644 index 6b9d8d565d..0000000000 --- a/assets/js/73f08e0f.ebb577a4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[3336],{15680:(e,t,o)=>{o.d(t,{xA:()=>p,yg:()=>y});var n=o(96540);function a(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function i(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function r(e){for(var t=1;t=0||(a[o]=e[o]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(a[o]=e[o])}return a}var s=n.createContext({}),g=function(e){var t=n.useContext(s),o=t;return e&&(o="function"==typeof e?e(t):r(r({},t),e)),o},p=function(e){var t=g(e.components);return n.createElement(s.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var o=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=g(o),d=a,y=u["".concat(s,".").concat(d)]||u[d]||c[d]||i;return o?n.createElement(y,r(r({ref:t},p),{},{components:o})):n.createElement(y,r({ref:t},p))}));function y(e,t){var o=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=o.length,r=new Array(i);r[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:a,r[1]=l;for(var g=2;g{o.r(t),o.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>g});var n=o(58168),a=(o(96540),o(15680));const i={authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},r="Introducing Detox Copilot",l={permalink:"/Detox/blog/2024/09/30/detox-copilot-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2024-09-30-detox-copilot-is-out.md",source:"@site/blog/2024-09-30-detox-copilot-is-out.md",title:"Introducing Detox Copilot",description:"Detox Copilot: Write Tests in Natural Language",date:"2024-09-30T00:00:00.000Z",formattedDate:"September 30, 2024",tags:[{label:"minor-release",permalink:"/Detox/blog/tags/minor-release"},{label:"detox-copilot",permalink:"/Detox/blog/tags/detox-copilot"},{label:"ai-integration",permalink:"/Detox/blog/tags/ai-integration"}],readingTime:3.27,hasTruncateMarker:!1,authors:[{name:"Asaf Korem",title:"Detox Core Contributor",url:"https://github.com/asafkorem",imageURL:"https://github.com/asafkorem.png",key:"asafkorem"}],frontMatter:{authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},nextItem:{title:"Detox 20 is out",permalink:"/Detox/blog/2022/11/10/detox-20-is-out"}},s={authorsImageUrls:[void 0]},g=[{value:"Detox Copilot: Write Tests in Natural Language",id:"detox-copilot-write-tests-in-natural-language",level:2},{value:"Why Natural Language Testing?",id:"why-natural-language-testing",level:3},{value:"Key Features of Detox Copilot",id:"key-features-of-detox-copilot",level:2},{value:"Write Tests in Plain Text",id:"write-tests-in-plain-text",level:3},{value:"Seamless Integration with Detox",id:"seamless-integration-with-detox",level:3},{value:"LLM-Agnostic Design",id:"llm-agnostic-design",level:3},{value:"How Detox Copilot Works",id:"how-detox-copilot-works",level:2},{value:"Getting Started with Detox Copilot",id:"getting-started-with-detox-copilot",level:2},{value:"Extending Beyond Detox",id:"extending-beyond-detox",level:2},{value:"Learn More",id:"learn-more",level:2},{value:"Join the Future of Testing",id:"join-the-future-of-testing",level:2}],p={toc:g},u="wrapper";function c(e){let{components:t,...i}=e;return(0,a.yg)(u,(0,n.A)({},p,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"detox-copilot-write-tests-in-natural-language"},"Detox Copilot: Write Tests in Natural Language"),(0,a.yg)("p",null,"We're excited to announce ",(0,a.yg)("strong",{parentName:"p"},"Detox Copilot"),", a groundbreaking feature that brings natural language testing to Detox. With Detox Copilot, you can now write end-to-end tests using plain textual commands, making test creation more intuitive and accessible than ever."),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Detox Copilot in action GIF",src:o(16031).A,width:"600",height:"388"})),(0,a.yg)("p",null,"Detox Copilot leverages advanced Large Language Models (LLMs) to interpret natural language instructions and translate them into Detox actions and assertions. This means you can describe your test scenarios in everyday language, aligning perfectly with ",(0,a.yg)("strong",{parentName:"p"},"Behavior-Driven Development (BDD)")," principles."),(0,a.yg)("h3",{id:"why-natural-language-testing"},"Why Natural Language Testing?"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Improved Collaboration"),": Teams can collaborate more effectively, as tests are written in plain language understandable by developers, QA engineers, and non-technical stakeholders alike."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Faster Test Creation"),": Reduce the time spent writing and maintaining complex test scripts."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Enhanced Test Coverage"),": Lower the barrier to writing tests, encouraging more comprehensive testing."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Reduced Maintenance Costs"),": Thanks to the decoupling from specific matchers (e.g., avoiding brittle XPath selectors or relying on ",(0,a.yg)("inlineCode",{parentName:"li"},"testID")," attributes commonly used in React Native apps), tests are less prone to breaking when the UI changes, leading to lower maintenance overhead.")),(0,a.yg)("h2",{id:"key-features-of-detox-copilot"},"Key Features of Detox Copilot"),(0,a.yg)("h3",{id:"write-tests-in-plain-text"},"Write Tests in Plain Text"),(0,a.yg)("p",null,"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should navigate and add a product to the cart', async () => {\n await copilot.perform(\n 'Navigate to the \"Products\" page',\n 'Tap on the \"Add to Cart\" button for the first product',\n 'Verify that the \"Added to Cart\" pop-up is displayed'\n );\n});\n")),(0,a.yg)("h3",{id:"seamless-integration-with-detox"},"Seamless Integration with Detox"),(0,a.yg)("p",null,"Detox Copilot is built into Detox and requires no additional installation. Simply initialize it in your test setup, and you're ready to start writing natural language tests."),(0,a.yg)("h3",{id:"llm-agnostic-design"},"LLM-Agnostic Design"),(0,a.yg)("p",null,"Detox Copilot uses LLMs to interpret instructions but is designed to be LLM-agnostic. This means you can connect it to your preferred language model service, offering flexibility and future-proofing your testing strategy."),(0,a.yg)("h2",{id:"how-detox-copilot-works"},"How Detox Copilot Works"),(0,a.yg)("p",null,"Once you've written your tests using natural language instructions, Detox Copilot takes care of the rest.\nHere is a high-level overview of the execution flow:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Gather Context"),": Collect relevant app state, view hierarchy, and previous step results."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Interpret Intent"),": Use the LLM to interpret the natural language instruction."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Generate Code"),": Create the appropriate Detox commands."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Execute Action"),": Run the generated Detox code."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Cache Results"),": Store execution results to optimize future runs."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Provide Feedback"),": Return values or confirm actions for subsequent steps.")),(0,a.yg)("p",null,"By combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions."),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},"Check Detox Copilot ",(0,a.yg)("strong",{parentName:"p"},(0,a.yg)("a",{parentName:"strong",href:"/docs/next/copilot/technical-overview"},"Technical Overview"))," for a detailed explanation of the building blocks and the execution flow.")),(0,a.yg)("h2",{id:"getting-started-with-detox-copilot"},"Getting Started with Detox Copilot"),(0,a.yg)("p",null,"Getting started with Detox Copilot is easy. Simply initialize Copilot in your test setup and start writing tests using natural language instructions."),(0,a.yg)("p",null,"Check our ",(0,a.yg)("a",{parentName:"p",href:"/docs/next/copilot/testing-with-copilot"},"Testing with Copilot guide")," for detailed instructions on setting up and writing tests with Detox Copilot."),(0,a.yg)("h2",{id:"extending-beyond-detox"},"Extending Beyond Detox"),(0,a.yg)("p",null,"Detox Copilot is built on a standalone core library called ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"detox-copilot")," designed to interpret natural language testing instructions and generate test code. Though initially developed for Detox, it can be extended to work with other testing frameworks."),(0,a.yg)("h2",{id:"learn-more"},"Learn More"),(0,a.yg)("p",null,"For detailed guidance, check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/next/copilot/testing-with-copilot"},"Testing with Copilot guide")," and the ",(0,a.yg)("a",{parentName:"p",href:"/docs/next/api/copilot"},"Detox Copilot API Documentation"),"."),(0,a.yg)("h2",{id:"join-the-future-of-testing"},"Join the Future of Testing"),(0,a.yg)("p",null,"Detox Copilot represents a major step forward in making end-to-end testing more accessible and efficient. By embracing natural language testing, you can enhance collaboration, speed up test creation, and improve overall test coverage."),(0,a.yg)("p",null,"We're ",(0,a.yg)("strong",{parentName:"p"},"excited")," to see how you'll leverage Detox Copilot in your tests! Share your experiences, feedback, and suggestions with us as we continue to refine and expand this groundbreaking feature."))}c.isMDXComponent=!0},16031:(e,t,o)=>{o.d(t,{A:()=>n});const n=o.p+"assets/images/copilot-demo-5c8641f24001c1d6e3effd175dc8ec2b.gif"}}]); \ No newline at end of file diff --git a/assets/js/814f3328.92f73eb0.js b/assets/js/814f3328.a1b90b7d.js similarity index 80% rename from assets/js/814f3328.92f73eb0.js rename to assets/js/814f3328.a1b90b7d.js index 4aad739d06..a350faebb4 100644 --- a/assets/js/814f3328.92f73eb0.js +++ b/assets/js/814f3328.a1b90b7d.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[7472],{55513:t=>{t.exports=JSON.parse('{"title":"All our posts","items":[{"title":"Introducing Detox Copilot","permalink":"/Detox/blog/2024/09/30/detox-copilot-is-out"},{"title":"Detox 20 is out","permalink":"/Detox/blog/2022/11/10/detox-20-is-out"}]}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[7472],{55513:t=>{t.exports=JSON.parse('{"title":"All our posts","items":[{"title":"Introducing Detox Copilot","permalink":"/Detox/blog/2024/10/09/detox-copilot-is-out"},{"title":"Detox 20 is out","permalink":"/Detox/blog/2022/11/10/detox-20-is-out"}]}')}}]); \ No newline at end of file diff --git a/assets/js/83eb08f7.c4aa751c.js b/assets/js/83eb08f7.c4aa751c.js new file mode 100644 index 0000000000..646e6269fe --- /dev/null +++ b/assets/js/83eb08f7.c4aa751c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2800],{15680:(e,t,o)=>{o.d(t,{xA:()=>p,yg:()=>y});var n=o(96540);function a(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function i(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function r(e){for(var t=1;t=0||(a[o]=e[o]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(a[o]=e[o])}return a}var s=n.createContext({}),g=function(e){var t=n.useContext(s),o=t;return e&&(o="function"==typeof e?e(t):r(r({},t),e)),o},p=function(e){var t=g(e.components);return n.createElement(s.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var o=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=g(o),d=a,y=u["".concat(s,".").concat(d)]||u[d]||c[d]||i;return o?n.createElement(y,r(r({ref:t},p),{},{components:o})):n.createElement(y,r({ref:t},p))}));function y(e,t){var o=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=o.length,r=new Array(i);r[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:a,r[1]=l;for(var g=2;g{o.r(t),o.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>g});var n=o(58168),a=(o(96540),o(15680));const i={authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},r="Introducing Detox Copilot",l={permalink:"/Detox/blog/2024/10/09/detox-copilot-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2024-10-09-detox-copilot-is-out.md",source:"@site/blog/2024-10-09-detox-copilot-is-out.md",title:"Introducing Detox Copilot",description:"Detox Copilot: Write Tests in Natural Language",date:"2024-10-09T00:00:00.000Z",formattedDate:"October 9, 2024",tags:[{label:"minor-release",permalink:"/Detox/blog/tags/minor-release"},{label:"detox-copilot",permalink:"/Detox/blog/tags/detox-copilot"},{label:"ai-integration",permalink:"/Detox/blog/tags/ai-integration"}],readingTime:3.27,hasTruncateMarker:!1,authors:[{name:"Asaf Korem",title:"Detox Core Contributor",url:"https://github.com/asafkorem",imageURL:"https://github.com/asafkorem.png",key:"asafkorem"}],frontMatter:{authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},nextItem:{title:"Detox 20 is out",permalink:"/Detox/blog/2022/11/10/detox-20-is-out"}},s={authorsImageUrls:[void 0]},g=[{value:"Detox Copilot: Write Tests in Natural Language",id:"detox-copilot-write-tests-in-natural-language",level:2},{value:"Why Natural Language Testing?",id:"why-natural-language-testing",level:3},{value:"Key Features of Detox Copilot",id:"key-features-of-detox-copilot",level:2},{value:"Write Tests in Plain Text",id:"write-tests-in-plain-text",level:3},{value:"Seamless Integration with Detox",id:"seamless-integration-with-detox",level:3},{value:"LLM-Agnostic Design",id:"llm-agnostic-design",level:3},{value:"How Detox Copilot Works",id:"how-detox-copilot-works",level:2},{value:"Getting Started with Detox Copilot",id:"getting-started-with-detox-copilot",level:2},{value:"Extending Beyond Detox",id:"extending-beyond-detox",level:2},{value:"Learn More",id:"learn-more",level:2},{value:"Join the Future of Testing",id:"join-the-future-of-testing",level:2}],p={toc:g},u="wrapper";function c(e){let{components:t,...i}=e;return(0,a.yg)(u,(0,n.A)({},p,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"detox-copilot-write-tests-in-natural-language"},"Detox Copilot: Write Tests in Natural Language"),(0,a.yg)("p",null,"We're excited to announce ",(0,a.yg)("strong",{parentName:"p"},"Detox Copilot"),", a groundbreaking feature that brings natural language testing to Detox. With Detox Copilot, you can now write end-to-end tests using plain textual commands, making test creation more intuitive and accessible than ever."),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Detox Copilot in action GIF",src:o(16031).A,width:"600",height:"388"})),(0,a.yg)("p",null,"Detox Copilot leverages advanced Large Language Models (LLMs) to interpret natural language instructions and translate them into Detox actions and assertions. This means you can describe your test scenarios in everyday language, aligning perfectly with ",(0,a.yg)("strong",{parentName:"p"},"Behavior-Driven Development (BDD)")," principles."),(0,a.yg)("h3",{id:"why-natural-language-testing"},"Why Natural Language Testing?"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Improved Collaboration"),": Teams can collaborate more effectively, as tests are written in plain language understandable by developers, QA engineers, and non-technical stakeholders alike."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Faster Test Creation"),": Reduce the time spent writing and maintaining complex test scripts."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Enhanced Test Coverage"),": Lower the barrier to writing tests, encouraging more comprehensive testing."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Reduced Maintenance Costs"),": Thanks to the decoupling from specific matchers (e.g., avoiding brittle XPath selectors or relying on ",(0,a.yg)("inlineCode",{parentName:"li"},"testID")," attributes commonly used in React Native apps), tests are less prone to breaking when the UI changes, leading to lower maintenance overhead.")),(0,a.yg)("h2",{id:"key-features-of-detox-copilot"},"Key Features of Detox Copilot"),(0,a.yg)("h3",{id:"write-tests-in-plain-text"},"Write Tests in Plain Text"),(0,a.yg)("p",null,"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should navigate and add a product to the cart', async () => {\n await copilot.perform(\n 'Navigate to the \"Products\" page',\n 'Tap on the \"Add to Cart\" button for the first product',\n 'Verify that the \"Added to Cart\" pop-up is displayed'\n );\n});\n")),(0,a.yg)("h3",{id:"seamless-integration-with-detox"},"Seamless Integration with Detox"),(0,a.yg)("p",null,"Detox Copilot is built into Detox and requires no additional installation. Simply initialize it in your test setup, and you're ready to start writing natural language tests."),(0,a.yg)("h3",{id:"llm-agnostic-design"},"LLM-Agnostic Design"),(0,a.yg)("p",null,"Detox Copilot uses LLMs to interpret instructions but is designed to be LLM-agnostic. This means you can connect it to your preferred language model service, offering flexibility and future-proofing your testing strategy."),(0,a.yg)("h2",{id:"how-detox-copilot-works"},"How Detox Copilot Works"),(0,a.yg)("p",null,"Once you've written your tests using natural language instructions, Detox Copilot takes care of the rest.\nHere is a high-level overview of the execution flow:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Gather Context"),": Collect relevant app state, view hierarchy, and previous step results."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Interpret Intent"),": Use the LLM to interpret the natural language instruction."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Generate Code"),": Create the appropriate Detox commands."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Execute Action"),": Run the generated Detox code."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Cache Results"),": Store execution results to optimize future runs."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Provide Feedback"),": Return values or confirm actions for subsequent steps.")),(0,a.yg)("p",null,"By combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions."),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},"Check Detox Copilot ",(0,a.yg)("strong",{parentName:"p"},(0,a.yg)("a",{parentName:"strong",href:"/docs/copilot/technical-overview"},"Technical Overview"))," for a detailed explanation of the building blocks and the execution flow.")),(0,a.yg)("h2",{id:"getting-started-with-detox-copilot"},"Getting Started with Detox Copilot"),(0,a.yg)("p",null,"Getting started with Detox Copilot is easy. Simply initialize Copilot in your test setup and start writing tests using natural language instructions."),(0,a.yg)("p",null,"Check our ",(0,a.yg)("a",{parentName:"p",href:"/docs/copilot/testing-with-copilot"},"Testing with Copilot guide")," for detailed instructions on setting up and writing tests with Detox Copilot."),(0,a.yg)("h2",{id:"extending-beyond-detox"},"Extending Beyond Detox"),(0,a.yg)("p",null,"Detox Copilot is built on a standalone core library called ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"detox-copilot")," designed to interpret natural language testing instructions and generate test code. Though initially developed for Detox, it can be extended to work with other testing frameworks."),(0,a.yg)("h2",{id:"learn-more"},"Learn More"),(0,a.yg)("p",null,"For detailed guidance, check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/copilot/testing-with-copilot"},"Testing with Copilot guide")," and the ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/copilot"},"Detox Copilot API Documentation"),"."),(0,a.yg)("h2",{id:"join-the-future-of-testing"},"Join the Future of Testing"),(0,a.yg)("p",null,"Detox Copilot represents a major step forward in making end-to-end testing more accessible and efficient. By embracing natural language testing, you can enhance collaboration, speed up test creation, and improve overall test coverage."),(0,a.yg)("p",null,"We're ",(0,a.yg)("strong",{parentName:"p"},"excited")," to see how you'll leverage Detox Copilot in your tests! Share your experiences, feedback, and suggestions with us as we continue to refine and expand this groundbreaking feature."))}c.isMDXComponent=!0},16031:(e,t,o)=>{o.d(t,{A:()=>n});const n=o.p+"assets/images/copilot-demo-5c8641f24001c1d6e3effd175dc8ec2b.gif"}}]); \ No newline at end of file diff --git a/assets/js/8f0fed8d.a227a311.js b/assets/js/8f0fed8d.a227a311.js new file mode 100644 index 0000000000..021787a062 --- /dev/null +++ b/assets/js/8f0fed8d.a227a311.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[1056],{15680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>m});var o=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 a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=o.createContext({}),p=function(e){var t=o.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},c=function(e){var t=p(e.components);return o.createElement(s.Provider,{value:t},e.children)},g="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},d=o.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),g=p(n),d=i,m=g["".concat(s,".").concat(d)]||g[d]||u[d]||a;return n?o.createElement(m,r(r({ref:t},c),{},{components:n})):o.createElement(m,r({ref:t},c))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,r=new Array(a);r[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[g]="string"==typeof e?e:i,r[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var o=n(58168),i=(n(96540),n(15680));const a={},r="Natural Language Testing with Detox Copilot",l={unversionedId:"copilot/testing-with-copilot",id:"version-20.x/copilot/testing-with-copilot",title:"Natural Language Testing with Detox Copilot",description:"In this tutorial, we'll explore how to use Detox Copilot to write end-to-end tests using natural language commands. Detox Copilot leverages large language models (LLMs) to translate human-readable instructions into Detox actions and assertions, making test writing more intuitive and accessible.",source:"@site/versioned_docs/version-20.x/copilot/testing-with-copilot.md",sourceDirName:"copilot",slug:"/copilot/testing-with-copilot",permalink:"/Detox/docs/copilot/testing-with-copilot",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/copilot/testing-with-copilot.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Uninstalling Detox",permalink:"/Detox/docs/guide/uninstalling"},next:{title:"Detox Copilot Best Practices",permalink:"/Detox/docs/copilot/best-practices"}},s={},p=[{value:"Introduction",id:"introduction",level:2},{value:"Setting Up Detox",id:"setting-up-detox",level:2},{value:"Implementing a PromptHandler",id:"implementing-a-prompthandler",level:2},{value:"Example of a PromptHandler for OpenAI's GPT-4",id:"example-of-a-prompthandler-for-openais-gpt-4",level:3},{value:"Initializing Detox Copilot",id:"initializing-detox-copilot",level:2},{value:"Writing Tests with Detox Copilot",id:"writing-tests-with-detox-copilot",level:2},{value:"Writing Step-by-Step Tests",id:"writing-step-by-step-tests",level:3},{value:"Hybrid Tests with Copilot and Detox APIs",id:"hybrid-tests-with-copilot-and-detox-apis",level:3},{value:"Locating Elements with Copilot",id:"locating-elements-with-copilot",level:3},{value:"Contributing to Detox Copilot",id:"contributing-to-detox-copilot",level:2}],c={toc:p},g="wrapper";function u(e){let{components:t,...a}=e;return(0,i.yg)(g,(0,o.A)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.yg)("h1",{id:"natural-language-testing-with-detox-copilot"},"Natural Language Testing with Detox Copilot"),(0,i.yg)("p",null,"In this tutorial, we'll explore how to use ",(0,i.yg)("strong",{parentName:"p"},"Detox Copilot")," to write end-to-end tests using natural language commands. Detox Copilot leverages large language models (LLMs) to translate human-readable instructions into Detox actions and assertions, making test writing more intuitive and accessible."),(0,i.yg)("admonition",{type:"note"},(0,i.yg)("p",{parentName:"admonition"},"Detox Copilot is integrated into Detox and requires no additional installation. For complete API details, refer to our ",(0,i.yg)("a",{parentName:"p",href:"/Detox/docs/api/copilot"},"Detox Copilot API documentation"),".")),(0,i.yg)("admonition",{title:"Work in Progress",type:"caution"},(0,i.yg)("p",{parentName:"admonition"},(0,i.yg)("strong",{parentName:"p"},"Note"),": Detox Copilot is in active development. APIs are subject to change in future releases.")),(0,i.yg)("h2",{id:"introduction"},"Introduction"),(0,i.yg)("p",null,"Detox Copilot simplifies the process of writing tests by allowing you to describe test steps in natural language.\nIt interprets these instructions and translates them into Detox commands. This guide will help you integrate Detox Copilot into your testing workflow and provide best practices for writing effective intents."),(0,i.yg)("p",null,(0,i.yg)("img",{alt:"Demo",src:n(36574).A,width:"800",height:"450"})),(0,i.yg)("h2",{id:"setting-up-detox"},"Setting Up Detox"),(0,i.yg)("p",null,"Before you begin, ensure that your Detox environment is properly set up.\nIf you need assistance with the setup, refer to the ",(0,i.yg)("a",{parentName:"p",href:"docs/introduction/getting-started/"},"Detox Getting Started Guide"),"."),(0,i.yg)("h2",{id:"implementing-a-prompthandler"},"Implementing a ",(0,i.yg)("inlineCode",{parentName:"h2"},"PromptHandler")),(0,i.yg)("p",null,"The ",(0,i.yg)("inlineCode",{parentName:"p"},"PromptHandler")," is a crucial component that interfaces with your LLM service.\nBelow is an example of how to implement a ",(0,i.yg)("inlineCode",{parentName:"p"},"PromptHandler")," using OpenAI's GPT-4 API."),(0,i.yg)("p",null,"You can adapt this code to work with other LLMs or services as needed. You may also find pre-built ",(0,i.yg)("inlineCode",{parentName:"p"},"PromptHandler")," implementations for popular LLMs in the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"detox-copilot repository on GitHub"),"."),(0,i.yg)("admonition",{title:"Open for Contributions",type:"note"},(0,i.yg)("p",{parentName:"admonition"},"If you have implemented a ",(0,i.yg)("inlineCode",{parentName:"p"},"PromptHandler")," for a specific LLM or service, consider contributing it to the Detox Copilot repository to help the community.")),(0,i.yg)("h3",{id:"example-of-a-prompthandler-for-openais-gpt-4"},"Example of a ",(0,i.yg)("inlineCode",{parentName:"h3"},"PromptHandler")," for OpenAI's GPT-4"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-javascript"},"const { Configuration, OpenAIApi } = require('openai');\nconst path = require('path');\n\nclass OpenAIPromptHandler {\n constructor(apiKey) {\n const configuration = new Configuration({ apiKey });\n this.openai = new OpenAIApi(configuration);\n }\n\n async runPrompt(prompt, imagePath) {\n const messages = [\n { role: 'system', content: 'You are a test automation assistant.' },\n { role: 'user', content: prompt },\n ];\n\n // If an image is provided, \"upload\" it and include the URL in the prompt\n if (imagePath && this.isSnapshotImageSupported()) {\n try {\n const imageUrl = await this.uploadImage(imagePath);\n messages.push({\n role: 'user',\n content: `Here is an image for reference: ${imageUrl}`,\n });\n } catch (error) {\n console.error('Error uploading image:', error);\n throw new Error('Failed to upload image');\n }\n }\n\n const response = await this.openai.createChatCompletion({\n model: 'gpt-4',\n messages,\n });\n\n return response.data.choices[0].message.content;\n }\n\n async uploadImage(imagePath) {\n // Uploads the image and returns the URL\n }\n\n isSnapshotImageSupported() {\n return true; // Set to true to handle image uploads\n }\n}\n\nmodule.exports = OpenAIPromptHandler;\n")),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"Explanation"),":"),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},(0,i.yg)("inlineCode",{parentName:"strong"},"runPrompt")),": Sends the prompt to the LLM and returns the response."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},(0,i.yg)("inlineCode",{parentName:"strong"},"isSnapshotImageSupported")),": Indicates whether the LLM can handle snapshot images. If set to ",(0,i.yg)("inlineCode",{parentName:"li"},"true"),", the handler will include image URLs in the prompt and will include them when instructing Detox Copilot.")),(0,i.yg)("h2",{id:"initializing-detox-copilot"},"Initializing Detox Copilot"),(0,i.yg)("p",null,"Initialize Detox Copilot with your ",(0,i.yg)("inlineCode",{parentName:"p"},"PromptHandler")," before running any tests.\nThis is typically done in the ",(0,i.yg)("inlineCode",{parentName:"p"},"beforeAll")," hook or a setup file."),(0,i.yg)("p",null,(0,i.yg)("strong",{parentName:"p"},"Example"),":"),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-javascript"},"const {copilot} = require('detox/index');\nconst OpenAIPromptHandler = require('./OpenAIPromptHandler');\n\nbeforeAll(() => {\n const promptHandler = new OpenAIPromptHandler('YOUR_OPENAI_API_KEY');\n copilot.init(promptHandler);\n});\n")),(0,i.yg)("h2",{id:"writing-tests-with-detox-copilot"},"Writing Tests with Detox Copilot"),(0,i.yg)("p",null,"With Detox Copilot initialized, you can now write tests using the ",(0,i.yg)("inlineCode",{parentName:"p"},"copilot.perform")," method."),(0,i.yg)("h3",{id:"writing-step-by-step-tests"},"Writing Step-by-Step Tests"),(0,i.yg)("p",null,"Detox Copilot allows you to write tests by providing a sequence of natural language instructions. Each instruction corresponds to a single action or assertion."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should verify element sizes and button states', async () => {\n await copilot.perform(\n 'Launch the app with notification permissions enabled',\n 'Navigate to the \"Settings\" page',\n 'Verify that the \"Save\" button is disabled',\n 'Locate the profile picture element',\n 'Verify that the profile picture size is 100 x 100 pixels and that the image is available and rendered',\n 'Tap on the \"Edit Profile\" button',\n 'Verify that the \"Save\" button is now enabled',\n 'Verify that the \"Username\" field text is bold'\n );\n});\n")),(0,i.yg)("p",null,"In the example above, Copilot can perform checks that go beyond traditional UI testing, such as verifying element sizes, button states (enabled/disabled), or text styles (e.g., bold). This is thanks to the combination of Detox code-generation and multimodal LLMs that can analyze the snapshots."),(0,i.yg)("ul",null,(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"Step-by-Step Instructions"),": Each step is a separate string, representing a single action or assertion."),(0,i.yg)("li",{parentName:"ul"},(0,i.yg)("strong",{parentName:"li"},"Sequential Execution"),": Steps are executed in order, allowing you to describe complex interactions intuitively.")),(0,i.yg)("h3",{id:"hybrid-tests-with-copilot-and-detox-apis"},"Hybrid Tests with Copilot and Detox APIs"),(0,i.yg)("p",null,"You can also combine Copilot commands with traditional Detox APIs for more control."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should add an item to the cart', async () => {\n await copilot.perform(\n 'Launch the app',\n 'Navigate to the \"Products\" page',\n 'Tap on the \"Add to Cart\" button for the first product'\n );\n\n const cartBadge = element(by.id('cart-badge'));\n await expect(cartBadge).toHaveText('1');\n\n await copilot.perform(\n 'Navigate to the \"Cart\" page',\n 'Verify that the product is listed in the cart'\n );\n});\n")),(0,i.yg)("h3",{id:"locating-elements-with-copilot"},"Locating Elements with Copilot"),(0,i.yg)("p",null,"You can also use Copilot to retrieve values, locate elements, or perform advanced checks such as verifying element sizes or button states."),(0,i.yg)("pre",null,(0,i.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should display the correct page title', async () => {\n const pageTitleElement = await copilot.perform(\n 'Launch the app',\n 'Navigate to the \"Profile\" page',\n 'Locate the page title element'\n );\n\n await expect(pageTitleElement).toHaveText('Profile');\n});\n")),(0,i.yg)("h2",{id:"contributing-to-detox-copilot"},"Contributing to Detox Copilot"),(0,i.yg)("p",null,"Contributions are welcome!\nVisit the ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"Detox Copilot GitHub Repository")," to open issues or pull requests if they are relevant to the core-library functionality or open a it under ",(0,i.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox"},"Detox repository")," if it is related to Detox-Copilot integration or if you are not sure where the issue should be opened."))}u.isMDXComponent=!0},36574:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/copilot-demo-6f1e5193e3cea07de196d443153fcd3a.gif"}}]); \ No newline at end of file diff --git a/assets/js/8f43d633.82509ee7.js b/assets/js/8f43d633.82509ee7.js new file mode 100644 index 0000000000..09c6702f2e --- /dev/null +++ b/assets/js/8f43d633.82509ee7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6990],{82885:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"/2024/10/09/detox-copilot-is-out","metadata":{"permalink":"/Detox/blog/2024/10/09/detox-copilot-is-out","editUrl":"https://github.com/wix/Detox/edit/master/website/blog/2024-10-09-detox-copilot-is-out.md","source":"@site/blog/2024-10-09-detox-copilot-is-out.md","title":"Introducing Detox Copilot","description":"Detox Copilot: Write Tests in Natural Language","date":"2024-10-09T00:00:00.000Z","formattedDate":"October 9, 2024","tags":[{"label":"minor-release","permalink":"/Detox/blog/tags/minor-release"},{"label":"detox-copilot","permalink":"/Detox/blog/tags/detox-copilot"},{"label":"ai-integration","permalink":"/Detox/blog/tags/ai-integration"}],"readingTime":3.27,"hasTruncateMarker":false,"authors":[{"name":"Asaf Korem","title":"Detox Core Contributor","url":"https://github.com/asafkorem","imageURL":"https://github.com/asafkorem.png","key":"asafkorem"}],"frontMatter":{"authors":["asafkorem"],"tags":["minor-release","detox-copilot","ai-integration"]},"nextItem":{"title":"Detox 20 is out","permalink":"/Detox/blog/2022/11/10/detox-20-is-out"}},"content":"## Detox Copilot: Write Tests in Natural Language\\n\\nWe\'re excited to announce **Detox Copilot**, a groundbreaking feature that brings natural language testing to Detox. With Detox Copilot, you can now write end-to-end tests using plain textual commands, making test creation more intuitive and accessible than ever.\\n\\n![Detox Copilot in action GIF](/img/blog/copilot-demo.gif)\\n\\nDetox Copilot leverages advanced Large Language Models (LLMs) to interpret natural language instructions and translate them into Detox actions and assertions. This means you can describe your test scenarios in everyday language, aligning perfectly with **Behavior-Driven Development (BDD)** principles.\\n\\n### Why Natural Language Testing?\\n\\n- **Improved Collaboration**: Teams can collaborate more effectively, as tests are written in plain language understandable by developers, QA engineers, and non-technical stakeholders alike.\\n- **Faster Test Creation**: Reduce the time spent writing and maintaining complex test scripts.\\n- **Enhanced Test Coverage**: Lower the barrier to writing tests, encouraging more comprehensive testing.\\n- **Reduced Maintenance Costs**: Thanks to the decoupling from specific matchers (e.g., avoiding brittle XPath selectors or relying on `testID` attributes commonly used in React Native apps), tests are less prone to breaking when the UI changes, leading to lower maintenance overhead.\\n\\n## Key Features of Detox Copilot\\n\\n### Write Tests in Plain Text\\n\\nDetox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app.\\n\\n```javascript\\nit(\'should navigate and add a product to the cart\', async () => {\\n await copilot.perform(\\n \'Navigate to the \\"Products\\" page\',\\n \'Tap on the \\"Add to Cart\\" button for the first product\',\\n \'Verify that the \\"Added to Cart\\" pop-up is displayed\'\\n );\\n});\\n```\\n\\n### Seamless Integration with Detox\\n\\nDetox Copilot is built into Detox and requires no additional installation. Simply initialize it in your test setup, and you\'re ready to start writing natural language tests.\\n\\n### LLM-Agnostic Design\\n\\nDetox Copilot uses LLMs to interpret instructions but is designed to be LLM-agnostic. This means you can connect it to your preferred language model service, offering flexibility and future-proofing your testing strategy.\\n\\n## How Detox Copilot Works\\n\\nOnce you\'ve written your tests using natural language instructions, Detox Copilot takes care of the rest.\\nHere is a high-level overview of the execution flow:\\n\\n1. **Gather Context**: Collect relevant app state, view hierarchy, and previous step results.\\n2. **Interpret Intent**: Use the LLM to interpret the natural language instruction.\\n3. **Generate Code**: Create the appropriate Detox commands.\\n4. **Execute Action**: Run the generated Detox code.\\n5. **Cache Results**: Store execution results to optimize future runs.\\n6. **Provide Feedback**: Return values or confirm actions for subsequent steps.\\n\\nBy combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions.\\n\\n:::info\\n\\nCheck Detox Copilot **[Technical Overview]** for a detailed explanation of the building blocks and the execution flow.\\n\\n:::\\n\\n## Getting Started with Detox Copilot\\n\\nGetting started with Detox Copilot is easy. Simply initialize Copilot in your test setup and start writing tests using natural language instructions.\\n\\nCheck our [Testing with Copilot guide] for detailed instructions on setting up and writing tests with Detox Copilot.\\n\\n\\n## Extending Beyond Detox\\n\\nDetox Copilot is built on a standalone core library called [detox-copilot] designed to interpret natural language testing instructions and generate test code. Though initially developed for Detox, it can be extended to work with other testing frameworks.\\n\\n## Learn More\\n\\nFor detailed guidance, check out our [Testing with Copilot guide] and the [Detox Copilot API Documentation].\\n\\n## Join the Future of Testing\\n\\nDetox Copilot represents a major step forward in making end-to-end testing more accessible and efficient. By embracing natural language testing, you can enhance collaboration, speed up test creation, and improve overall test coverage.\\n\\nWe\'re **excited** to see how you\'ll leverage Detox Copilot in your tests! Share your experiences, feedback, and suggestions with us as we continue to refine and expand this groundbreaking feature.\\n\\n[Testing with Copilot guide]: /docs/copilot/testing-with-copilot\\n[Detox Copilot API Documentation]: /docs/api/copilot\\n[detox-copilot]: https://github.com/wix-incubator/detox-copilot\\n[Technical Overview]: /docs/copilot/technical-overview"},{"id":"/2022/11/10/detox-20-is-out","metadata":{"permalink":"/Detox/blog/2022/11/10/detox-20-is-out","editUrl":"https://github.com/wix/Detox/edit/master/website/blog/2022-11-10-detox-20-is-out.md","source":"@site/blog/2022-11-10-detox-20-is-out.md","title":"Detox 20 is out","description":"Today we\'re proud to announce the new major release, Detox 20 (codename \\"Ash\xe1n\\"), which brings:","date":"2022-11-10T00:00:00.000Z","formattedDate":"November 10, 2022","tags":[{"label":"major-release","permalink":"/Detox/blog/tags/major-release"},{"label":"genymotion","permalink":"/Detox/blog/tags/genymotion"}],"readingTime":9.705,"hasTruncateMarker":false,"authors":[{"name":"Yaroslav Serhieiev","title":"Detox Core Contributor","url":"https://github.com/noomorph","imageURL":"https://github.com/noomorph.png","key":"noomorph"}],"frontMatter":{"authors":["noomorph"],"tags":["major-release","genymotion"]},"prevItem":{"title":"Introducing Detox Copilot","permalink":"/Detox/blog/2024/10/09/detox-copilot-is-out"}},"content":"Today we\'re proud to announce the new major release, **Detox 20** (codename \\"Ash\xe1n\\"), which brings:\\n\\n* official support for Genymotion SaaS\\n* improved integration with test runners\\n* configurable logging subsystem\\n* headless mode for iOS via configs and CLI\\n* reversing TCP ports via Android app configs\\n* and more optimizations to land in the next minor versions.\\n\\n## Genymotion SaaS\\n\\n**Highlights**: [`Using Genymotion SaaS`].\\n\\nTwo years ago we [added elementary support][Genymotion PR] for cloud-based Android emulators provided by [Genymotion SaaS] and started a beta testing phase across mobile projects at Wix.\\n\\nPreviously our mobile infrastructure engineers had been maintaining Android virtual devices on CI build agents on their own, so switching to cloud devices cleared up their time for more productive tasks. Another improvement was particularly noticeable for teams with a vast number of tests. We could reduce the duration of their CI pipelines almost by half after they scaled up from 2 parallel devices to 6[^1].\\n\\n[^1]: The mentioned threshold is not a hard limit, but rather a point where the return value of scaling up the number of devices starts dramatically diminishing in our case \u2013 not only the tests themselves, but installing NPM dependencies and building the projects also takes time.\\n\\nThis positive impact encouraged us to adopt [Genymotion SaaS] for CI as quickly as possible, ignoring some unresolved issues in the initial pull request. For the most part, those were minor problems in global lifecycle management. Yet that made us feel uncertain about releasing it as-is, so we decided to take time and gain more production experience before taking any direction.\\n\\nThe further experience was surprisingly smooth and rarely presented issues, spare for a few minor [glitches](https://github.com/wix/Detox/issues/3573) in advanced scenarios. Admittedly, revamping the [Detox lifecycle][`Internals API`] took us longer than expected, which is all the more reason for us to celebrate today.\\n\\nWe\'re looking forward to providing our users with more opportunities for testing in the cloud, and this step is only the first of many to come. We hope you\'ll utilize this new feature to your delight.\\n\\n## Integration with test runners\\n\\n**Highlights**: [`Config file > Test runner`], [`Internals API`], [`Dropping Mocha support`].\\n\\nIt took about a few months of work to formalize the contract between Detox and a test runner. While there\'s still a lot of place for improvement, the new Detox release refines their interaction and lays the groundwork for third-party integrations.\\n\\n[Mocha] was our first supported test runner, but unfortunately, it could not keep up with our scaling requirements as the number of end-to-end tests grew. [By the time][Mocha 8] it acquired the ability to run tests in parallel, we already had [to place bets][Jest PR] on another horse, and that was [Jest].\\n\\nWe attempted to keep compatibility with both Jest and Mocha, but the farther we went, the more obvious it was that we couldn\'t have it both ways. As it turned out, Jest wasn\'t easy to get along with \u2013 our first integration with it was too simplistic. Over a couple of years of use in production, we kept discovering various issues that forced us to rewrite our \\"glue\\" code from scratch twice, and this isn\'t over yet. All combined didn\'t leave much time and energy for tinkering with Mocha anymore.\\n\\nIn this release, we discontinued Mocha support to focus on the attunement of Jest with the new runner-independent [test runner config][`Config file > Test runner`] and [Internals API][`Internals API`]. If there\'s enough demand, now it is up to the open-source community to build a new integration between Detox and Mocha.\\n\\n## Configurable logger\\n\\n**Highlights**: [`Config file > Logger`], [`Logger API`].\\n\\nThe rigidity of the logging subsystem has always been showing itself [since its very creation][Logger rewrite] in the summer of 2019.\\nDue to time constraints and existing tech debts, it was impossible to do it right the first time, so we lived about three years with a proof-of-concept rather than a full-fledged feature.\\n\\nThe inconveniences weren\'t fatal but quite noticeable, nevertheless. Here are a few syndromes you could have spotted if you have ever used Detox timeline and log artifacts, especially when running tests in parallel:\\n\\n* an uncanny file array: `detox_pid_7505.log`, `detox_pid_7505.log.json`, `detox_pid_7506.log`;\\n* a relatively shallow `detox.trace.json`: test suites, test functions, and some user-defined segments.\\n\\nThe good news is that the new Detox release condenses all those numerous logs into two files:\\n\\n* the plain, human-readable `detox.log`;\\n* the raw, machine-readable `detox.trace.json` for `chrome://trace`, [Perfetto] and other utilities.\\n\\n![A screenshot of timeline view generated by Perfetto](/img/blog/v20-perfetto-example.png)\\n\\nWith the help of the new [Logger API][`Logger API`], you can add custom duration events to the timeline, too, e.g.:\\n\\n```js\\nawait detox.log.trace.complete(\'Login\', async () => {\\n await element(by.id(\'email\')).typeText(\'john@example.com\');\\n await element(by.id(\'password\')).typeText(\'123456\');\\n\\n detox.log.info(\'Trying to log in...\');\\n await element(by.id(\'submit\')).tap();\\n});\\n```\\n\\nBesides, it is possible now to customize the console output of Detox via the new [logger config][`Config file > Logger`], e.g.:\\n\\n```js title=\\"detox.config.js\\"\\n/** @type {Detox.DetoxConfig} */\\nmodule.exports = {\\n // ...\\n logger: {\\n options: {\\n showDate: false,\\n showLoggerName: false,\\n showPid: false,\\n prefixers: {\\n ph: null,\\n },\\n },\\n },\\n};\\n```\\n\\nIn the example above, we minimize all the metadata around the log messages \u2013 see the screenshot below:\\n\\n![Terser logs after applying the override](/img/blog/v20-logger-options.png)\\n\\n## Minor features\\n\\n### Headless iOS\\n\\nOne of Detox known issues was always booting iOS simulators in a hidden mode. You could see tests running on your local simulator only if you had manually opened the Simulator app beforehand. So, we unified the `headless` property for both iOS and Android, and now both the platforms visibly boot a device unless you configure it otherwise, e.g.:\\n\\n```js\\n/* @type {Detox.DetoxConfig} */\\nmodule.exports = {\\n devices: {\\n iphone: {\\n type: \'ios.simulator\',\\n // highlight-next-line\\n headless: process.env.CI ? true : undefined,\\n device: {\\n type: \'iPhone 14\'\\n },\\n /* ... */\\n }\\n },\\n};\\n```\\n\\nor, via CLI:\\n\\n```bash\\ndetox test -c ios.sim.release --headless\\n```\\n\\n### Reverse ports\\n\\nYour apps might try to access some `localhost:*` addresses (e.g., mock servers), but this is a bit more problematic in the case of Android. The Android emulators are separate virtual devices with their own loopback network interface. In such cases, you must set up reverse port forwarding via `adb reverse`.\\n\\nLocal servers are quite a common prerequisite for apps in debug mode \u2013 one could recall React Native bundler on port 8081, Storybook server on 9009, etc. That\'s why we decided to add an optional config property for Android apps, `reversePorts`:\\n\\n```js\\n/** @type {Detox.DetoxConfig} */\\nmodule.exports = {\\n // ...\\n apps: {\\n \'android.debug\': {\\n type: \'android.apk\',\\n binaryPath: \'...\',\\n reversePorts: [8081, 3000],\\n },\\n },\\n};\\n```\\n\\nIn other words, this is a convenience API that tells Detox to run `device.reverseTcpPort(portNumber)` after installing the app. It should be helpful for anyone who prefers to keep such things as configs rather than as code.\\n\\n### Read-only emulators by default\\n\\nThe `-read-only` flag appeared in [Android emulator 28.0.16](https://developer.android.com/studio/releases/emulator#concurrent-avd). Detox promptly adopted it since the read-only mode allowed it to run multiple instances of a single Android virtual device (AVD) concurrently. This feature helped us to implement parallel test execution support for Android back then.\\n\\nBeing overcautious, we implemented that partially, only for cases when the user starts multiple concurrent workers. This decision created a moderately annoying UX issue. Imagine you run tests sequentially first, using one worker only. That provides you with a regular AVD instance, i.e., not a read-only one. After that, you switch to multiple workers only to get an error from the Android emulator, complaining about mixing regular and read-only instances.\\n\\nWhile the fix itself has always been straightforward \u2013 close the running AVD and try again \u2013 this entire overcaution brought more issues than solving them. That\'s why, from now on, Android emulators will always be starting in `-read-only` mode unless you configure `readonly: false` in your [device config][`Config file > Device`].\\n\\n### Reset lock file\\n\\nThis release adds a small CLI tool, [`detox reset-lock-file`][reset-lock-file], to help users with one specific use scenario.\\n\\nImagine you want to run tests for multiple Detox configurations at once, e.g.:\\n\\n```bash\\ndetox test -c iphoneSE2020.release e2e/ui.test.js\\ndetox test -c iphone14ProMax.release e2e/ui.test.js\\n```\\n\\nThe problem is that Detox uses a file-locking mechanism to avoid situations when parallel test workers would take control of the same device. The `detox test` command, upon start, erases that file contents, creating a race condition risk.\\n\\nTo eliminate that risk, use a combination of `detox reset-lock-file` and `--keepLockFile` like this:\\n\\n```bash\\ndetox reset-lock-file & \\\\\\ndetox test --keepLockFile -c iphoneSE2020.release e2e/ui.test.js & \\\\\\ndetox test --keepLockFile -c iphone14ProMax.release e2e/ui.test.js & \\\\\\nwait\\n```\\n\\nIn the future, we plan to minimize using lock files so that you don\'t have to think about this low-level implementation detail.\\nSo, this tool adds some convenience until we provide a next-gen solution.\\n\\n## Deprecations\\n\\nDetox 20 executes many pending deprecations, so make sure to check out our [Migration Guide] before upgrading:\\n\\n* JS: minimum supported Node.js version is `14.x`;\\n* JS: minimum supported Jest version is `27.2.5`;\\n* JS: Mocha test runner is no longer supported;\\n* JS: discontinued old adapters for Jest (`jest-jasmine`, first generation of `jest-circus` adapter);\\n* JS: discontinued `{ permanent: true }` option in `device.appLaunchArgs.*` methods ([#3360](https://github.com/wix/Detox/pull/3360));\\n* CLI: dropped `-w, --workers` and `-o, --runner-config` args \u2013 see a [dedicated section][Updating command-line scripts] in the migration guide;\\n* CLI: dropped deprecated `--device-launch-args` ([#3665](https://github.com/wix/Detox/pull/3665));\\n* Config: discontinued kebab-case properties: `test-runner`, `runner-config` ([#3371](https://github.com/wix/Detox/pull/3371))\\n* Config: discontinued `skipLegacyWorkersInjections` property ([(#3286)](https://github.com/wix/Detox/pull/3286))\\n* Config: deprecated `specs` and `runnerConfig` properties\\n* Config: changed [semantics][`Config file > Test runner`] of `testRunner` property\\n* Config: dropped support for all-in-one configurations ([#3386](https://github.com/wix/Detox/pull/3386));\\n* Android: remove deprecated native IdlePolicyConfig ([#3332](https://github.com/wix/Detox/pull/3332/files))\\n* iOS: discontinued `ios.none` device type \u2013 see the new way to [debug native code][Debugging Native] ([#3361](https://github.com/wix/Detox/pull/3361))\\n\\n## Afterword\\n\\nOver the last year and a half, we have established a centralized configuration system for more than 50 projects using Detox at Wix. While it never seemed to be a cakewalk, the entire experience of troubleshooting over a hundred issues across the organization did not leave us unchanged.\\n\\nWe see numerous things to improve in Detox, but most of them boil down to the same thing \u2013 **scaling**. Surprisingly, \\"scaling\\" makes an excellent umbrella term for nearly every challenge we\'ve been encountering lately:\\n\\n* _scaling up the number of users_ requires us to improve the onboarding and troubleshooting experience;\\n* _scaling up the number of projects_ forces us to centralize scattered configs into flexible organization presets;\\n* _scaling up the number of tests_ prompts us to optimize the codebase and incline it towards cloud and remote execution.\\n\\nOur core team has been facing challenges of limited human resource constraints and growing scaling needs for a long time. In many ways, that has shaped a specific mindset within the team. We evaluate every discussed feature by asking a simple question: _will it save other people and us time to focus on more important things?_ Teaching a man to fish is better than giving fish, so our success at preventing support issues matters more than our success at solving them ourselves.\\n\\nThat\'s why we\'ll be making subsequent efforts in these three areas, hoping to get back to you soon with even more exciting updates.\\n\\nEnjoy your drive with Detox 20!\\n\\nCheers! :wave:\\n\\n[`Using Genymotion SaaS`]: /docs/guide/genymotion-saas\\n[`Config file > Test runner`]: /docs/config/testRunner\\n[`Internals API`]: /docs/api/internals\\n[`Dropping Mocha support`]: https://github.com/wix/Detox/issues/3193\\n[`Config file > Device`]: /docs/config/devices\\n[`Config file > Logger`]: /docs/config/logger\\n[`Config file > Test runner`]: /docs/config/testRunner\\n[`Logger API`]: /docs/api/logger\\n[reset-lock-file]: /docs/cli/reset-lock-file\\n[Jest]: https://jestjs.io\\n[Mocha]: https://mochajs.org\\n[Mocha 8]: https://github.com/mochajs/mocha/releases/tag/v8.0.0\\n[Jest PR]: https://github.com/wix/Detox/pull/609\\n[Logger rewrite]: https://github.com/wix/Detox/pull/835\\n[Genymotion PR]: https://github.com/wix/Detox/pull/2446\\n[Genymotion SaaS]: https://cloud.geny.io\\n[Genymotion issues]: https://github.com/wix/Detox/issues/3573\\n[Perfetto]: https://ui.perfetto.dev/\\n[Migration Guide]: /docs/guide/migration#200\\n[Updating command-line scripts]: /docs/guide/migration#updating-command-line-scripts\\n[Debugging Native]: /docs/introduction/debugging#native-application-code\\n[typings]: https://github.com/wix/Detox/blob/master/detox/index.d.ts"}]}')}}]); \ No newline at end of file diff --git a/assets/js/8f43d633.99f04e1b.js b/assets/js/8f43d633.99f04e1b.js deleted file mode 100644 index 48cd08dff2..0000000000 --- a/assets/js/8f43d633.99f04e1b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6990],{82885:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"/2024/09/30/detox-copilot-is-out","metadata":{"permalink":"/Detox/blog/2024/09/30/detox-copilot-is-out","editUrl":"https://github.com/wix/Detox/edit/master/website/blog/2024-09-30-detox-copilot-is-out.md","source":"@site/blog/2024-09-30-detox-copilot-is-out.md","title":"Introducing Detox Copilot","description":"Detox Copilot: Write Tests in Natural Language","date":"2024-09-30T00:00:00.000Z","formattedDate":"September 30, 2024","tags":[{"label":"minor-release","permalink":"/Detox/blog/tags/minor-release"},{"label":"detox-copilot","permalink":"/Detox/blog/tags/detox-copilot"},{"label":"ai-integration","permalink":"/Detox/blog/tags/ai-integration"}],"readingTime":3.27,"hasTruncateMarker":false,"authors":[{"name":"Asaf Korem","title":"Detox Core Contributor","url":"https://github.com/asafkorem","imageURL":"https://github.com/asafkorem.png","key":"asafkorem"}],"frontMatter":{"authors":["asafkorem"],"tags":["minor-release","detox-copilot","ai-integration"]},"nextItem":{"title":"Detox 20 is out","permalink":"/Detox/blog/2022/11/10/detox-20-is-out"}},"content":"## Detox Copilot: Write Tests in Natural Language\\n\\nWe\'re excited to announce **Detox Copilot**, a groundbreaking feature that brings natural language testing to Detox. With Detox Copilot, you can now write end-to-end tests using plain textual commands, making test creation more intuitive and accessible than ever.\\n\\n![Detox Copilot in action GIF](/img/blog/copilot-demo.gif)\\n\\nDetox Copilot leverages advanced Large Language Models (LLMs) to interpret natural language instructions and translate them into Detox actions and assertions. This means you can describe your test scenarios in everyday language, aligning perfectly with **Behavior-Driven Development (BDD)** principles.\\n\\n### Why Natural Language Testing?\\n\\n- **Improved Collaboration**: Teams can collaborate more effectively, as tests are written in plain language understandable by developers, QA engineers, and non-technical stakeholders alike.\\n- **Faster Test Creation**: Reduce the time spent writing and maintaining complex test scripts.\\n- **Enhanced Test Coverage**: Lower the barrier to writing tests, encouraging more comprehensive testing.\\n- **Reduced Maintenance Costs**: Thanks to the decoupling from specific matchers (e.g., avoiding brittle XPath selectors or relying on `testID` attributes commonly used in React Native apps), tests are less prone to breaking when the UI changes, leading to lower maintenance overhead.\\n\\n## Key Features of Detox Copilot\\n\\n### Write Tests in Plain Text\\n\\nDetox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app.\\n\\n```javascript\\nit(\'should navigate and add a product to the cart\', async () => {\\n await copilot.perform(\\n \'Navigate to the \\"Products\\" page\',\\n \'Tap on the \\"Add to Cart\\" button for the first product\',\\n \'Verify that the \\"Added to Cart\\" pop-up is displayed\'\\n );\\n});\\n```\\n\\n### Seamless Integration with Detox\\n\\nDetox Copilot is built into Detox and requires no additional installation. Simply initialize it in your test setup, and you\'re ready to start writing natural language tests.\\n\\n### LLM-Agnostic Design\\n\\nDetox Copilot uses LLMs to interpret instructions but is designed to be LLM-agnostic. This means you can connect it to your preferred language model service, offering flexibility and future-proofing your testing strategy.\\n\\n## How Detox Copilot Works\\n\\nOnce you\'ve written your tests using natural language instructions, Detox Copilot takes care of the rest.\\nHere is a high-level overview of the execution flow:\\n\\n1. **Gather Context**: Collect relevant app state, view hierarchy, and previous step results.\\n2. **Interpret Intent**: Use the LLM to interpret the natural language instruction.\\n3. **Generate Code**: Create the appropriate Detox commands.\\n4. **Execute Action**: Run the generated Detox code.\\n5. **Cache Results**: Store execution results to optimize future runs.\\n6. **Provide Feedback**: Return values or confirm actions for subsequent steps.\\n\\nBy combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions.\\n\\n:::info\\n\\nCheck Detox Copilot **[Technical Overview]** for a detailed explanation of the building blocks and the execution flow.\\n\\n:::\\n\\n## Getting Started with Detox Copilot\\n\\nGetting started with Detox Copilot is easy. Simply initialize Copilot in your test setup and start writing tests using natural language instructions.\\n\\nCheck our [Testing with Copilot guide] for detailed instructions on setting up and writing tests with Detox Copilot.\\n\\n\\n## Extending Beyond Detox\\n\\nDetox Copilot is built on a standalone core library called [detox-copilot] designed to interpret natural language testing instructions and generate test code. Though initially developed for Detox, it can be extended to work with other testing frameworks.\\n\\n## Learn More\\n\\nFor detailed guidance, check out our [Testing with Copilot guide] and the [Detox Copilot API Documentation].\\n\\n## Join the Future of Testing\\n\\nDetox Copilot represents a major step forward in making end-to-end testing more accessible and efficient. By embracing natural language testing, you can enhance collaboration, speed up test creation, and improve overall test coverage.\\n\\nWe\'re **excited** to see how you\'ll leverage Detox Copilot in your tests! Share your experiences, feedback, and suggestions with us as we continue to refine and expand this groundbreaking feature.\\n\\n[Testing with Copilot guide]: /docs/next/copilot/testing-with-copilot\\n[Detox Copilot API Documentation]: /docs/next/api/copilot\\n[detox-copilot]: https://github.com/wix-incubator/detox-copilot\\n[Technical Overview]: /docs/next/copilot/technical-overview"},{"id":"/2022/11/10/detox-20-is-out","metadata":{"permalink":"/Detox/blog/2022/11/10/detox-20-is-out","editUrl":"https://github.com/wix/Detox/edit/master/website/blog/2022-11-10-detox-20-is-out.md","source":"@site/blog/2022-11-10-detox-20-is-out.md","title":"Detox 20 is out","description":"Today we\'re proud to announce the new major release, Detox 20 (codename \\"Ash\xe1n\\"), which brings:","date":"2022-11-10T00:00:00.000Z","formattedDate":"November 10, 2022","tags":[{"label":"major-release","permalink":"/Detox/blog/tags/major-release"},{"label":"genymotion","permalink":"/Detox/blog/tags/genymotion"}],"readingTime":9.705,"hasTruncateMarker":false,"authors":[{"name":"Yaroslav Serhieiev","title":"Detox Core Contributor","url":"https://github.com/noomorph","imageURL":"https://github.com/noomorph.png","key":"noomorph"}],"frontMatter":{"authors":["noomorph"],"tags":["major-release","genymotion"]},"prevItem":{"title":"Introducing Detox Copilot","permalink":"/Detox/blog/2024/09/30/detox-copilot-is-out"}},"content":"Today we\'re proud to announce the new major release, **Detox 20** (codename \\"Ash\xe1n\\"), which brings:\\n\\n* official support for Genymotion SaaS\\n* improved integration with test runners\\n* configurable logging subsystem\\n* headless mode for iOS via configs and CLI\\n* reversing TCP ports via Android app configs\\n* and more optimizations to land in the next minor versions.\\n\\n## Genymotion SaaS\\n\\n**Highlights**: [`Using Genymotion SaaS`].\\n\\nTwo years ago we [added elementary support][Genymotion PR] for cloud-based Android emulators provided by [Genymotion SaaS] and started a beta testing phase across mobile projects at Wix.\\n\\nPreviously our mobile infrastructure engineers had been maintaining Android virtual devices on CI build agents on their own, so switching to cloud devices cleared up their time for more productive tasks. Another improvement was particularly noticeable for teams with a vast number of tests. We could reduce the duration of their CI pipelines almost by half after they scaled up from 2 parallel devices to 6[^1].\\n\\n[^1]: The mentioned threshold is not a hard limit, but rather a point where the return value of scaling up the number of devices starts dramatically diminishing in our case \u2013 not only the tests themselves, but installing NPM dependencies and building the projects also takes time.\\n\\nThis positive impact encouraged us to adopt [Genymotion SaaS] for CI as quickly as possible, ignoring some unresolved issues in the initial pull request. For the most part, those were minor problems in global lifecycle management. Yet that made us feel uncertain about releasing it as-is, so we decided to take time and gain more production experience before taking any direction.\\n\\nThe further experience was surprisingly smooth and rarely presented issues, spare for a few minor [glitches](https://github.com/wix/Detox/issues/3573) in advanced scenarios. Admittedly, revamping the [Detox lifecycle][`Internals API`] took us longer than expected, which is all the more reason for us to celebrate today.\\n\\nWe\'re looking forward to providing our users with more opportunities for testing in the cloud, and this step is only the first of many to come. We hope you\'ll utilize this new feature to your delight.\\n\\n## Integration with test runners\\n\\n**Highlights**: [`Config file > Test runner`], [`Internals API`], [`Dropping Mocha support`].\\n\\nIt took about a few months of work to formalize the contract between Detox and a test runner. While there\'s still a lot of place for improvement, the new Detox release refines their interaction and lays the groundwork for third-party integrations.\\n\\n[Mocha] was our first supported test runner, but unfortunately, it could not keep up with our scaling requirements as the number of end-to-end tests grew. [By the time][Mocha 8] it acquired the ability to run tests in parallel, we already had [to place bets][Jest PR] on another horse, and that was [Jest].\\n\\nWe attempted to keep compatibility with both Jest and Mocha, but the farther we went, the more obvious it was that we couldn\'t have it both ways. As it turned out, Jest wasn\'t easy to get along with \u2013 our first integration with it was too simplistic. Over a couple of years of use in production, we kept discovering various issues that forced us to rewrite our \\"glue\\" code from scratch twice, and this isn\'t over yet. All combined didn\'t leave much time and energy for tinkering with Mocha anymore.\\n\\nIn this release, we discontinued Mocha support to focus on the attunement of Jest with the new runner-independent [test runner config][`Config file > Test runner`] and [Internals API][`Internals API`]. If there\'s enough demand, now it is up to the open-source community to build a new integration between Detox and Mocha.\\n\\n## Configurable logger\\n\\n**Highlights**: [`Config file > Logger`], [`Logger API`].\\n\\nThe rigidity of the logging subsystem has always been showing itself [since its very creation][Logger rewrite] in the summer of 2019.\\nDue to time constraints and existing tech debts, it was impossible to do it right the first time, so we lived about three years with a proof-of-concept rather than a full-fledged feature.\\n\\nThe inconveniences weren\'t fatal but quite noticeable, nevertheless. Here are a few syndromes you could have spotted if you have ever used Detox timeline and log artifacts, especially when running tests in parallel:\\n\\n* an uncanny file array: `detox_pid_7505.log`, `detox_pid_7505.log.json`, `detox_pid_7506.log`;\\n* a relatively shallow `detox.trace.json`: test suites, test functions, and some user-defined segments.\\n\\nThe good news is that the new Detox release condenses all those numerous logs into two files:\\n\\n* the plain, human-readable `detox.log`;\\n* the raw, machine-readable `detox.trace.json` for `chrome://trace`, [Perfetto] and other utilities.\\n\\n![A screenshot of timeline view generated by Perfetto](/img/blog/v20-perfetto-example.png)\\n\\nWith the help of the new [Logger API][`Logger API`], you can add custom duration events to the timeline, too, e.g.:\\n\\n```js\\nawait detox.log.trace.complete(\'Login\', async () => {\\n await element(by.id(\'email\')).typeText(\'john@example.com\');\\n await element(by.id(\'password\')).typeText(\'123456\');\\n\\n detox.log.info(\'Trying to log in...\');\\n await element(by.id(\'submit\')).tap();\\n});\\n```\\n\\nBesides, it is possible now to customize the console output of Detox via the new [logger config][`Config file > Logger`], e.g.:\\n\\n```js title=\\"detox.config.js\\"\\n/** @type {Detox.DetoxConfig} */\\nmodule.exports = {\\n // ...\\n logger: {\\n options: {\\n showDate: false,\\n showLoggerName: false,\\n showPid: false,\\n prefixers: {\\n ph: null,\\n },\\n },\\n },\\n};\\n```\\n\\nIn the example above, we minimize all the metadata around the log messages \u2013 see the screenshot below:\\n\\n![Terser logs after applying the override](/img/blog/v20-logger-options.png)\\n\\n## Minor features\\n\\n### Headless iOS\\n\\nOne of Detox known issues was always booting iOS simulators in a hidden mode. You could see tests running on your local simulator only if you had manually opened the Simulator app beforehand. So, we unified the `headless` property for both iOS and Android, and now both the platforms visibly boot a device unless you configure it otherwise, e.g.:\\n\\n```js\\n/* @type {Detox.DetoxConfig} */\\nmodule.exports = {\\n devices: {\\n iphone: {\\n type: \'ios.simulator\',\\n // highlight-next-line\\n headless: process.env.CI ? true : undefined,\\n device: {\\n type: \'iPhone 14\'\\n },\\n /* ... */\\n }\\n },\\n};\\n```\\n\\nor, via CLI:\\n\\n```bash\\ndetox test -c ios.sim.release --headless\\n```\\n\\n### Reverse ports\\n\\nYour apps might try to access some `localhost:*` addresses (e.g., mock servers), but this is a bit more problematic in the case of Android. The Android emulators are separate virtual devices with their own loopback network interface. In such cases, you must set up reverse port forwarding via `adb reverse`.\\n\\nLocal servers are quite a common prerequisite for apps in debug mode \u2013 one could recall React Native bundler on port 8081, Storybook server on 9009, etc. That\'s why we decided to add an optional config property for Android apps, `reversePorts`:\\n\\n```js\\n/** @type {Detox.DetoxConfig} */\\nmodule.exports = {\\n // ...\\n apps: {\\n \'android.debug\': {\\n type: \'android.apk\',\\n binaryPath: \'...\',\\n reversePorts: [8081, 3000],\\n },\\n },\\n};\\n```\\n\\nIn other words, this is a convenience API that tells Detox to run `device.reverseTcpPort(portNumber)` after installing the app. It should be helpful for anyone who prefers to keep such things as configs rather than as code.\\n\\n### Read-only emulators by default\\n\\nThe `-read-only` flag appeared in [Android emulator 28.0.16](https://developer.android.com/studio/releases/emulator#concurrent-avd). Detox promptly adopted it since the read-only mode allowed it to run multiple instances of a single Android virtual device (AVD) concurrently. This feature helped us to implement parallel test execution support for Android back then.\\n\\nBeing overcautious, we implemented that partially, only for cases when the user starts multiple concurrent workers. This decision created a moderately annoying UX issue. Imagine you run tests sequentially first, using one worker only. That provides you with a regular AVD instance, i.e., not a read-only one. After that, you switch to multiple workers only to get an error from the Android emulator, complaining about mixing regular and read-only instances.\\n\\nWhile the fix itself has always been straightforward \u2013 close the running AVD and try again \u2013 this entire overcaution brought more issues than solving them. That\'s why, from now on, Android emulators will always be starting in `-read-only` mode unless you configure `readonly: false` in your [device config][`Config file > Device`].\\n\\n### Reset lock file\\n\\nThis release adds a small CLI tool, [`detox reset-lock-file`][reset-lock-file], to help users with one specific use scenario.\\n\\nImagine you want to run tests for multiple Detox configurations at once, e.g.:\\n\\n```bash\\ndetox test -c iphoneSE2020.release e2e/ui.test.js\\ndetox test -c iphone14ProMax.release e2e/ui.test.js\\n```\\n\\nThe problem is that Detox uses a file-locking mechanism to avoid situations when parallel test workers would take control of the same device. The `detox test` command, upon start, erases that file contents, creating a race condition risk.\\n\\nTo eliminate that risk, use a combination of `detox reset-lock-file` and `--keepLockFile` like this:\\n\\n```bash\\ndetox reset-lock-file & \\\\\\ndetox test --keepLockFile -c iphoneSE2020.release e2e/ui.test.js & \\\\\\ndetox test --keepLockFile -c iphone14ProMax.release e2e/ui.test.js & \\\\\\nwait\\n```\\n\\nIn the future, we plan to minimize using lock files so that you don\'t have to think about this low-level implementation detail.\\nSo, this tool adds some convenience until we provide a next-gen solution.\\n\\n## Deprecations\\n\\nDetox 20 executes many pending deprecations, so make sure to check out our [Migration Guide] before upgrading:\\n\\n* JS: minimum supported Node.js version is `14.x`;\\n* JS: minimum supported Jest version is `27.2.5`;\\n* JS: Mocha test runner is no longer supported;\\n* JS: discontinued old adapters for Jest (`jest-jasmine`, first generation of `jest-circus` adapter);\\n* JS: discontinued `{ permanent: true }` option in `device.appLaunchArgs.*` methods ([#3360](https://github.com/wix/Detox/pull/3360));\\n* CLI: dropped `-w, --workers` and `-o, --runner-config` args \u2013 see a [dedicated section][Updating command-line scripts] in the migration guide;\\n* CLI: dropped deprecated `--device-launch-args` ([#3665](https://github.com/wix/Detox/pull/3665));\\n* Config: discontinued kebab-case properties: `test-runner`, `runner-config` ([#3371](https://github.com/wix/Detox/pull/3371))\\n* Config: discontinued `skipLegacyWorkersInjections` property ([(#3286)](https://github.com/wix/Detox/pull/3286))\\n* Config: deprecated `specs` and `runnerConfig` properties\\n* Config: changed [semantics][`Config file > Test runner`] of `testRunner` property\\n* Config: dropped support for all-in-one configurations ([#3386](https://github.com/wix/Detox/pull/3386));\\n* Android: remove deprecated native IdlePolicyConfig ([#3332](https://github.com/wix/Detox/pull/3332/files))\\n* iOS: discontinued `ios.none` device type \u2013 see the new way to [debug native code][Debugging Native] ([#3361](https://github.com/wix/Detox/pull/3361))\\n\\n## Afterword\\n\\nOver the last year and a half, we have established a centralized configuration system for more than 50 projects using Detox at Wix. While it never seemed to be a cakewalk, the entire experience of troubleshooting over a hundred issues across the organization did not leave us unchanged.\\n\\nWe see numerous things to improve in Detox, but most of them boil down to the same thing \u2013 **scaling**. Surprisingly, \\"scaling\\" makes an excellent umbrella term for nearly every challenge we\'ve been encountering lately:\\n\\n* _scaling up the number of users_ requires us to improve the onboarding and troubleshooting experience;\\n* _scaling up the number of projects_ forces us to centralize scattered configs into flexible organization presets;\\n* _scaling up the number of tests_ prompts us to optimize the codebase and incline it towards cloud and remote execution.\\n\\nOur core team has been facing challenges of limited human resource constraints and growing scaling needs for a long time. In many ways, that has shaped a specific mindset within the team. We evaluate every discussed feature by asking a simple question: _will it save other people and us time to focus on more important things?_ Teaching a man to fish is better than giving fish, so our success at preventing support issues matters more than our success at solving them ourselves.\\n\\nThat\'s why we\'ll be making subsequent efforts in these three areas, hoping to get back to you soon with even more exciting updates.\\n\\nEnjoy your drive with Detox 20!\\n\\nCheers! :wave:\\n\\n[`Using Genymotion SaaS`]: /docs/guide/genymotion-saas\\n[`Config file > Test runner`]: /docs/config/testRunner\\n[`Internals API`]: /docs/api/internals\\n[`Dropping Mocha support`]: https://github.com/wix/Detox/issues/3193\\n[`Config file > Device`]: /docs/config/devices\\n[`Config file > Logger`]: /docs/config/logger\\n[`Config file > Test runner`]: /docs/config/testRunner\\n[`Logger API`]: /docs/api/logger\\n[reset-lock-file]: /docs/cli/reset-lock-file\\n[Jest]: https://jestjs.io\\n[Mocha]: https://mochajs.org\\n[Mocha 8]: https://github.com/mochajs/mocha/releases/tag/v8.0.0\\n[Jest PR]: https://github.com/wix/Detox/pull/609\\n[Logger rewrite]: https://github.com/wix/Detox/pull/835\\n[Genymotion PR]: https://github.com/wix/Detox/pull/2446\\n[Genymotion SaaS]: https://cloud.geny.io\\n[Genymotion issues]: https://github.com/wix/Detox/issues/3573\\n[Perfetto]: https://ui.perfetto.dev/\\n[Migration Guide]: /docs/guide/migration#200\\n[Updating command-line scripts]: /docs/guide/migration#updating-command-line-scripts\\n[Debugging Native]: /docs/introduction/debugging#native-application-code\\n[typings]: https://github.com/wix/Detox/blob/master/detox/index.d.ts"}]}')}}]); \ No newline at end of file diff --git a/assets/js/91864373.f10c7f86.js b/assets/js/91864373.f10c7f86.js new file mode 100644 index 0000000000..d91cf89243 --- /dev/null +++ b/assets/js/91864373.f10c7f86.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6482],{15680:(e,t,o)=>{o.d(t,{xA:()=>p,yg:()=>y});var n=o(96540);function a(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function i(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function r(e){for(var t=1;t=0||(a[o]=e[o]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(a[o]=e[o])}return a}var s=n.createContext({}),g=function(e){var t=n.useContext(s),o=t;return e&&(o="function"==typeof e?e(t):r(r({},t),e)),o},p=function(e){var t=g(e.components);return n.createElement(s.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var o=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=g(o),d=a,y=u["".concat(s,".").concat(d)]||u[d]||c[d]||i;return o?n.createElement(y,r(r({ref:t},p),{},{components:o})):n.createElement(y,r({ref:t},p))}));function y(e,t){var o=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=o.length,r=new Array(i);r[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:a,r[1]=l;for(var g=2;g{o.r(t),o.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>g});var n=o(58168),a=(o(96540),o(15680));const i={authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},r="Introducing Detox Copilot",l={permalink:"/Detox/blog/2024/10/09/detox-copilot-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2024-10-09-detox-copilot-is-out.md",source:"@site/blog/2024-10-09-detox-copilot-is-out.md",title:"Introducing Detox Copilot",description:"Detox Copilot: Write Tests in Natural Language",date:"2024-10-09T00:00:00.000Z",formattedDate:"October 9, 2024",tags:[{label:"minor-release",permalink:"/Detox/blog/tags/minor-release"},{label:"detox-copilot",permalink:"/Detox/blog/tags/detox-copilot"},{label:"ai-integration",permalink:"/Detox/blog/tags/ai-integration"}],readingTime:3.27,hasTruncateMarker:!1,authors:[{name:"Asaf Korem",title:"Detox Core Contributor",url:"https://github.com/asafkorem",imageURL:"https://github.com/asafkorem.png",key:"asafkorem"}],frontMatter:{authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},nextItem:{title:"Detox 20 is out",permalink:"/Detox/blog/2022/11/10/detox-20-is-out"}},s={authorsImageUrls:[void 0]},g=[{value:"Detox Copilot: Write Tests in Natural Language",id:"detox-copilot-write-tests-in-natural-language",level:2},{value:"Why Natural Language Testing?",id:"why-natural-language-testing",level:3},{value:"Key Features of Detox Copilot",id:"key-features-of-detox-copilot",level:2},{value:"Write Tests in Plain Text",id:"write-tests-in-plain-text",level:3},{value:"Seamless Integration with Detox",id:"seamless-integration-with-detox",level:3},{value:"LLM-Agnostic Design",id:"llm-agnostic-design",level:3},{value:"How Detox Copilot Works",id:"how-detox-copilot-works",level:2},{value:"Getting Started with Detox Copilot",id:"getting-started-with-detox-copilot",level:2},{value:"Extending Beyond Detox",id:"extending-beyond-detox",level:2},{value:"Learn More",id:"learn-more",level:2},{value:"Join the Future of Testing",id:"join-the-future-of-testing",level:2}],p={toc:g},u="wrapper";function c(e){let{components:t,...i}=e;return(0,a.yg)(u,(0,n.A)({},p,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"detox-copilot-write-tests-in-natural-language"},"Detox Copilot: Write Tests in Natural Language"),(0,a.yg)("p",null,"We're excited to announce ",(0,a.yg)("strong",{parentName:"p"},"Detox Copilot"),", a groundbreaking feature that brings natural language testing to Detox. With Detox Copilot, you can now write end-to-end tests using plain textual commands, making test creation more intuitive and accessible than ever."),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Detox Copilot in action GIF",src:o(16031).A,width:"600",height:"388"})),(0,a.yg)("p",null,"Detox Copilot leverages advanced Large Language Models (LLMs) to interpret natural language instructions and translate them into Detox actions and assertions. This means you can describe your test scenarios in everyday language, aligning perfectly with ",(0,a.yg)("strong",{parentName:"p"},"Behavior-Driven Development (BDD)")," principles."),(0,a.yg)("h3",{id:"why-natural-language-testing"},"Why Natural Language Testing?"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Improved Collaboration"),": Teams can collaborate more effectively, as tests are written in plain language understandable by developers, QA engineers, and non-technical stakeholders alike."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Faster Test Creation"),": Reduce the time spent writing and maintaining complex test scripts."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Enhanced Test Coverage"),": Lower the barrier to writing tests, encouraging more comprehensive testing."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Reduced Maintenance Costs"),": Thanks to the decoupling from specific matchers (e.g., avoiding brittle XPath selectors or relying on ",(0,a.yg)("inlineCode",{parentName:"li"},"testID")," attributes commonly used in React Native apps), tests are less prone to breaking when the UI changes, leading to lower maintenance overhead.")),(0,a.yg)("h2",{id:"key-features-of-detox-copilot"},"Key Features of Detox Copilot"),(0,a.yg)("h3",{id:"write-tests-in-plain-text"},"Write Tests in Plain Text"),(0,a.yg)("p",null,"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should navigate and add a product to the cart', async () => {\n await copilot.perform(\n 'Navigate to the \"Products\" page',\n 'Tap on the \"Add to Cart\" button for the first product',\n 'Verify that the \"Added to Cart\" pop-up is displayed'\n );\n});\n")),(0,a.yg)("h3",{id:"seamless-integration-with-detox"},"Seamless Integration with Detox"),(0,a.yg)("p",null,"Detox Copilot is built into Detox and requires no additional installation. Simply initialize it in your test setup, and you're ready to start writing natural language tests."),(0,a.yg)("h3",{id:"llm-agnostic-design"},"LLM-Agnostic Design"),(0,a.yg)("p",null,"Detox Copilot uses LLMs to interpret instructions but is designed to be LLM-agnostic. This means you can connect it to your preferred language model service, offering flexibility and future-proofing your testing strategy."),(0,a.yg)("h2",{id:"how-detox-copilot-works"},"How Detox Copilot Works"),(0,a.yg)("p",null,"Once you've written your tests using natural language instructions, Detox Copilot takes care of the rest.\nHere is a high-level overview of the execution flow:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Gather Context"),": Collect relevant app state, view hierarchy, and previous step results."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Interpret Intent"),": Use the LLM to interpret the natural language instruction."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Generate Code"),": Create the appropriate Detox commands."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Execute Action"),": Run the generated Detox code."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Cache Results"),": Store execution results to optimize future runs."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Provide Feedback"),": Return values or confirm actions for subsequent steps.")),(0,a.yg)("p",null,"By combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions."),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},"Check Detox Copilot ",(0,a.yg)("strong",{parentName:"p"},(0,a.yg)("a",{parentName:"strong",href:"/docs/copilot/technical-overview"},"Technical Overview"))," for a detailed explanation of the building blocks and the execution flow.")),(0,a.yg)("h2",{id:"getting-started-with-detox-copilot"},"Getting Started with Detox Copilot"),(0,a.yg)("p",null,"Getting started with Detox Copilot is easy. Simply initialize Copilot in your test setup and start writing tests using natural language instructions."),(0,a.yg)("p",null,"Check our ",(0,a.yg)("a",{parentName:"p",href:"/docs/copilot/testing-with-copilot"},"Testing with Copilot guide")," for detailed instructions on setting up and writing tests with Detox Copilot."),(0,a.yg)("h2",{id:"extending-beyond-detox"},"Extending Beyond Detox"),(0,a.yg)("p",null,"Detox Copilot is built on a standalone core library called ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"detox-copilot")," designed to interpret natural language testing instructions and generate test code. Though initially developed for Detox, it can be extended to work with other testing frameworks."),(0,a.yg)("h2",{id:"learn-more"},"Learn More"),(0,a.yg)("p",null,"For detailed guidance, check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/copilot/testing-with-copilot"},"Testing with Copilot guide")," and the ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/copilot"},"Detox Copilot API Documentation"),"."),(0,a.yg)("h2",{id:"join-the-future-of-testing"},"Join the Future of Testing"),(0,a.yg)("p",null,"Detox Copilot represents a major step forward in making end-to-end testing more accessible and efficient. By embracing natural language testing, you can enhance collaboration, speed up test creation, and improve overall test coverage."),(0,a.yg)("p",null,"We're ",(0,a.yg)("strong",{parentName:"p"},"excited")," to see how you'll leverage Detox Copilot in your tests! Share your experiences, feedback, and suggestions with us as we continue to refine and expand this groundbreaking feature."))}c.isMDXComponent=!0},16031:(e,t,o)=>{o.d(t,{A:()=>n});const n=o.p+"assets/images/copilot-demo-5c8641f24001c1d6e3effd175dc8ec2b.gif"}}]); \ No newline at end of file diff --git a/assets/js/9292650b.b0d5ad62.js b/assets/js/9292650b.7ee07628.js similarity index 99% rename from assets/js/9292650b.b0d5ad62.js rename to assets/js/9292650b.7ee07628.js index f6e826ec5c..86669b6f09 100644 --- a/assets/js/9292650b.b0d5ad62.js +++ b/assets/js/9292650b.7ee07628.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[9034],{15680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>c});var o=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=o.createContext({}),p=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},u=function(e){var t=p(e.components);return o.createElement(l.Provider,{value:t},e.children)},g="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},m=o.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),g=p(n),m=a,c=g["".concat(l,".").concat(m)]||g[m]||d[m]||i;return n?o.createElement(c,r(r({ref:t},u),{},{components:n})):o.createElement(c,r({ref:t},u))}));function c(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,r=new Array(i);r[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[g]="string"==typeof e?e:a,r[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var o=n(58168),a=(n(96540),n(15680));const i={authors:["noomorph"],tags:["major-release","genymotion"]},r="Detox 20 is out",s={permalink:"/Detox/blog/2022/11/10/detox-20-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2022-11-10-detox-20-is-out.md",source:"@site/blog/2022-11-10-detox-20-is-out.md",title:"Detox 20 is out",description:'Today we\'re proud to announce the new major release, Detox 20 (codename "Ash\xe1n"), which brings:',date:"2022-11-10T00:00:00.000Z",formattedDate:"November 10, 2022",tags:[{label:"major-release",permalink:"/Detox/blog/tags/major-release"},{label:"genymotion",permalink:"/Detox/blog/tags/genymotion"}],readingTime:9.705,hasTruncateMarker:!1,authors:[{name:"Yaroslav Serhieiev",title:"Detox Core Contributor",url:"https://github.com/noomorph",imageURL:"https://github.com/noomorph.png",key:"noomorph"}],frontMatter:{authors:["noomorph"],tags:["major-release","genymotion"]},prevItem:{title:"Introducing Detox Copilot",permalink:"/Detox/blog/2024/09/30/detox-copilot-is-out"}},l={authorsImageUrls:[void 0]},p=[{value:"Genymotion SaaS",id:"genymotion-saas",level:2},{value:"Integration with test runners",id:"integration-with-test-runners",level:2},{value:"Configurable logger",id:"configurable-logger",level:2},{value:"Minor features",id:"minor-features",level:2},{value:"Headless iOS",id:"headless-ios",level:3},{value:"Reverse ports",id:"reverse-ports",level:3},{value:"Read-only emulators by default",id:"read-only-emulators-by-default",level:3},{value:"Reset lock file",id:"reset-lock-file",level:3},{value:"Deprecations",id:"deprecations",level:2},{value:"Afterword",id:"afterword",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...i}=e;return(0,a.yg)(g,(0,o.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"Today we're proud to announce the new major release, ",(0,a.yg)("strong",{parentName:"p"},"Detox 20"),' (codename "Ash\xe1n"), which brings:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"official support for Genymotion SaaS"),(0,a.yg)("li",{parentName:"ul"},"improved integration with test runners"),(0,a.yg)("li",{parentName:"ul"},"configurable logging subsystem"),(0,a.yg)("li",{parentName:"ul"},"headless mode for iOS via configs and CLI"),(0,a.yg)("li",{parentName:"ul"},"reversing TCP ports via Android app configs"),(0,a.yg)("li",{parentName:"ul"},"and more optimizations to land in the next minor versions.")),(0,a.yg)("h2",{id:"genymotion-saas"},"Genymotion SaaS"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/genymotion-saas"},(0,a.yg)("inlineCode",{parentName:"a"},"Using Genymotion SaaS")),"."),(0,a.yg)("p",null,"Two years ago we ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/2446"},"added elementary support")," for cloud-based Android emulators provided by ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," and started a beta testing phase across mobile projects at Wix."),(0,a.yg)("p",null,"Previously our mobile infrastructure engineers had been maintaining Android virtual devices on CI build agents on their own, so switching to cloud devices cleared up their time for more productive tasks. Another improvement was particularly noticeable for teams with a vast number of tests. We could reduce the duration of their CI pipelines almost by half after they scaled up from 2 parallel devices to 6",(0,a.yg)("sup",{parentName:"p",id:"fnref-1-2b8cfe"},(0,a.yg)("a",{parentName:"sup",href:"#fn-1-2b8cfe",className:"footnote-ref"},"1")),"."),(0,a.yg)("p",null,"This positive impact encouraged us to adopt ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," for CI as quickly as possible, ignoring some unresolved issues in the initial pull request. For the most part, those were minor problems in global lifecycle management. Yet that made us feel uncertain about releasing it as-is, so we decided to take time and gain more production experience before taking any direction."),(0,a.yg)("p",null,"The further experience was surprisingly smooth and rarely presented issues, spare for a few minor ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3573"},"glitches")," in advanced scenarios. Admittedly, revamping the ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Detox lifecycle")," took us longer than expected, which is all the more reason for us to celebrate today."),(0,a.yg)("p",null,"We're looking forward to providing our users with more opportunities for testing in the cloud, and this step is only the first of many to come. We hope you'll utilize this new feature to your delight."),(0,a.yg)("h2",{id:"integration-with-test-runners"},"Integration with test runners"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Test runner")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},(0,a.yg)("inlineCode",{parentName:"a"},"Internals API")),", ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3193"},(0,a.yg)("inlineCode",{parentName:"a"},"Dropping Mocha support")),"."),(0,a.yg)("p",null,"It took about a few months of work to formalize the contract between Detox and a test runner. While there's still a lot of place for improvement, the new Detox release refines their interaction and lays the groundwork for third-party integrations."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://mochajs.org"},"Mocha")," was our first supported test runner, but unfortunately, it could not keep up with our scaling requirements as the number of end-to-end tests grew. ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/mochajs/mocha/releases/tag/v8.0.0"},"By the time")," it acquired the ability to run tests in parallel, we already had ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/609"},"to place bets")," on another horse, and that was ",(0,a.yg)("a",{parentName:"p",href:"https://jestjs.io"},"Jest"),"."),(0,a.yg)("p",null,"We attempted to keep compatibility with both Jest and Mocha, but the farther we went, the more obvious it was that we couldn't have it both ways. As it turned out, Jest wasn't easy to get along with \u2013 our first integration with it was too simplistic. Over a couple of years of use in production, we kept discovering various issues that forced us to rewrite our \"glue\" code from scratch twice, and this isn't over yet. All combined didn't leave much time and energy for tinkering with Mocha anymore."),(0,a.yg)("p",null,"In this release, we discontinued Mocha support to focus on the attunement of Jest with the new runner-independent ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},"test runner config")," and ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Internals API"),". If there's enough demand, now it is up to the open-source community to build a new integration between Detox and Mocha."),(0,a.yg)("h2",{id:"configurable-logger"},"Configurable logger"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Logger")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Logger API")),"."),(0,a.yg)("p",null,"The rigidity of the logging subsystem has always been showing itself ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/835"},"since its very creation")," in the summer of 2019.\nDue to time constraints and existing tech debts, it was impossible to do it right the first time, so we lived about three years with a proof-of-concept rather than a full-fledged feature."),(0,a.yg)("p",null,"The inconveniences weren't fatal but quite noticeable, nevertheless. Here are a few syndromes you could have spotted if you have ever used Detox timeline and log artifacts, especially when running tests in parallel:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"an uncanny file array: ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log.json"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7506.log"),";"),(0,a.yg)("li",{parentName:"ul"},"a relatively shallow ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json"),": test suites, test functions, and some user-defined segments.")),(0,a.yg)("p",null,"The good news is that the new Detox release condenses all those numerous logs into two files:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"the plain, human-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.log"),";"),(0,a.yg)("li",{parentName:"ul"},"the raw, machine-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json")," for ",(0,a.yg)("inlineCode",{parentName:"li"},"chrome://trace"),", ",(0,a.yg)("a",{parentName:"li",href:"https://ui.perfetto.dev/"},"Perfetto")," and other utilities.")),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"A screenshot of timeline view generated by Perfetto",src:n(27709).A,width:"1600",height:"1000"})),(0,a.yg)("p",null,"With the help of the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},"Logger API"),", you can add custom duration events to the timeline, too, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"await detox.log.trace.complete('Login', async () => {\n await element(by.id('email')).typeText('john@example.com');\n await element(by.id('password')).typeText('123456');\n\n detox.log.info('Trying to log in...');\n await element(by.id('submit')).tap();\n});\n")),(0,a.yg)("p",null,"Besides, it is possible now to customize the console output of Detox via the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},"logger config"),", e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox.config.js"',title:'"detox.config.js"'},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n logger: {\n options: {\n showDate: false,\n showLoggerName: false,\n showPid: false,\n prefixers: {\n ph: null,\n },\n },\n },\n};\n")),(0,a.yg)("p",null,"In the example above, we minimize all the metadata around the log messages \u2013 see the screenshot below:"),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Terser logs after applying the override",src:n(72716).A,width:"1400",height:"399"})),(0,a.yg)("h2",{id:"minor-features"},"Minor features"),(0,a.yg)("h3",{id:"headless-ios"},"Headless iOS"),(0,a.yg)("p",null,"One of Detox known issues was always booting iOS simulators in a hidden mode. You could see tests running on your local simulator only if you had manually opened the Simulator app beforehand. So, we unified the ",(0,a.yg)("inlineCode",{parentName:"p"},"headless")," property for both iOS and Android, and now both the platforms visibly boot a device unless you configure it otherwise, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/* @type {Detox.DetoxConfig} */\nmodule.exports = {\n devices: {\n iphone: {\n type: 'ios.simulator',\n // highlight-next-line\n headless: process.env.CI ? true : undefined,\n device: {\n type: 'iPhone 14'\n },\n /* ... */\n }\n },\n};\n")),(0,a.yg)("p",null,"or, via CLI:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c ios.sim.release --headless\n")),(0,a.yg)("h3",{id:"reverse-ports"},"Reverse ports"),(0,a.yg)("p",null,"Your apps might try to access some ",(0,a.yg)("inlineCode",{parentName:"p"},"localhost:*")," addresses (e.g., mock servers), but this is a bit more problematic in the case of Android. The Android emulators are separate virtual devices with their own loopback network interface. In such cases, you must set up reverse port forwarding via ",(0,a.yg)("inlineCode",{parentName:"p"},"adb reverse"),"."),(0,a.yg)("p",null,"Local servers are quite a common prerequisite for apps in debug mode \u2013 one could recall React Native bundler on port 8081, Storybook server on 9009, etc. That's why we decided to add an optional config property for Android apps, ",(0,a.yg)("inlineCode",{parentName:"p"},"reversePorts"),":"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n apps: {\n 'android.debug': {\n type: 'android.apk',\n binaryPath: '...',\n reversePorts: [8081, 3000],\n },\n },\n};\n")),(0,a.yg)("p",null,"In other words, this is a convenience API that tells Detox to run ",(0,a.yg)("inlineCode",{parentName:"p"},"device.reverseTcpPort(portNumber)")," after installing the app. It should be helpful for anyone who prefers to keep such things as configs rather than as code."),(0,a.yg)("h3",{id:"read-only-emulators-by-default"},"Read-only emulators by default"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," flag appeared in ",(0,a.yg)("a",{parentName:"p",href:"https://developer.android.com/studio/releases/emulator#concurrent-avd"},"Android emulator 28.0.16"),". Detox promptly adopted it since the read-only mode allowed it to run multiple instances of a single Android virtual device (AVD) concurrently. This feature helped us to implement parallel test execution support for Android back then."),(0,a.yg)("p",null,"Being overcautious, we implemented that partially, only for cases when the user starts multiple concurrent workers. This decision created a moderately annoying UX issue. Imagine you run tests sequentially first, using one worker only. That provides you with a regular AVD instance, i.e., not a read-only one. After that, you switch to multiple workers only to get an error from the Android emulator, complaining about mixing regular and read-only instances."),(0,a.yg)("p",null,"While the fix itself has always been straightforward \u2013 close the running AVD and try again \u2013 this entire overcaution brought more issues than solving them. That's why, from now on, Android emulators will always be starting in ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," mode unless you configure ",(0,a.yg)("inlineCode",{parentName:"p"},"readonly: false")," in your ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/devices"},"device config"),"."),(0,a.yg)("h3",{id:"reset-lock-file"},"Reset lock file"),(0,a.yg)("p",null,"This release adds a small CLI tool, ",(0,a.yg)("a",{parentName:"p",href:"/docs/cli/reset-lock-file"},(0,a.yg)("inlineCode",{parentName:"a"},"detox reset-lock-file")),", to help users with one specific use scenario."),(0,a.yg)("p",null,"Imagine you want to run tests for multiple Detox configurations at once, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c iphoneSE2020.release e2e/ui.test.js\ndetox test -c iphone14ProMax.release e2e/ui.test.js\n")),(0,a.yg)("p",null,"The problem is that Detox uses a file-locking mechanism to avoid situations when parallel test workers would take control of the same device. The ",(0,a.yg)("inlineCode",{parentName:"p"},"detox test")," command, upon start, erases that file contents, creating a race condition risk."),(0,a.yg)("p",null,"To eliminate that risk, use a combination of ",(0,a.yg)("inlineCode",{parentName:"p"},"detox reset-lock-file")," and ",(0,a.yg)("inlineCode",{parentName:"p"},"--keepLockFile")," like this:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox reset-lock-file & \\\ndetox test --keepLockFile -c iphoneSE2020.release e2e/ui.test.js & \\\ndetox test --keepLockFile -c iphone14ProMax.release e2e/ui.test.js & \\\nwait\n")),(0,a.yg)("p",null,"In the future, we plan to minimize using lock files so that you don't have to think about this low-level implementation detail.\nSo, this tool adds some convenience until we provide a next-gen solution."),(0,a.yg)("h2",{id:"deprecations"},"Deprecations"),(0,a.yg)("p",null,"Detox 20 executes many pending deprecations, so make sure to check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/migration#200"},"Migration Guide")," before upgrading:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Node.js version is ",(0,a.yg)("inlineCode",{parentName:"li"},"14.x"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Jest version is ",(0,a.yg)("inlineCode",{parentName:"li"},"27.2.5"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: Mocha test runner is no longer supported;"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued old adapters for Jest (",(0,a.yg)("inlineCode",{parentName:"li"},"jest-jasmine"),", first generation of ",(0,a.yg)("inlineCode",{parentName:"li"},"jest-circus")," adapter);"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"{ permanent: true }")," option in ",(0,a.yg)("inlineCode",{parentName:"li"},"device.appLaunchArgs.*")," methods (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3360"},"#3360"),");"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped ",(0,a.yg)("inlineCode",{parentName:"li"},"-w, --workers")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"-o, --runner-config")," args \u2013 see a ",(0,a.yg)("a",{parentName:"li",href:"/docs/guide/migration#updating-command-line-scripts"},"dedicated section")," in the migration guide;"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"--device-launch-args")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3665"},"#3665"),");"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued kebab-case properties: ",(0,a.yg)("inlineCode",{parentName:"li"},"test-runner"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"runner-config")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3371"},"#3371"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"skipLegacyWorkersInjections")," property (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3286"},"(#3286)"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"specs")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"runnerConfig")," properties"),(0,a.yg)("li",{parentName:"ul"},"Config: changed ",(0,a.yg)("a",{parentName:"li",href:"/docs/config/testRunner"},"semantics")," of ",(0,a.yg)("inlineCode",{parentName:"li"},"testRunner")," property"),(0,a.yg)("li",{parentName:"ul"},"Config: dropped support for all-in-one configurations (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3386"},"#3386"),");"),(0,a.yg)("li",{parentName:"ul"},"Android: remove deprecated native IdlePolicyConfig (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3332/files"},"#3332"),")"),(0,a.yg)("li",{parentName:"ul"},"iOS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"ios.none")," device type \u2013 see the new way to ",(0,a.yg)("a",{parentName:"li",href:"/docs/introduction/debugging#native-application-code"},"debug native code")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3361"},"#3361"),")")),(0,a.yg)("h2",{id:"afterword"},"Afterword"),(0,a.yg)("p",null,"Over the last year and a half, we have established a centralized configuration system for more than 50 projects using Detox at Wix. While it never seemed to be a cakewalk, the entire experience of troubleshooting over a hundred issues across the organization did not leave us unchanged."),(0,a.yg)("p",null,"We see numerous things to improve in Detox, but most of them boil down to the same thing \u2013 ",(0,a.yg)("strong",{parentName:"p"},"scaling"),'. Surprisingly, "scaling" makes an excellent umbrella term for nearly every challenge we\'ve been encountering lately:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of users")," requires us to improve the onboarding and troubleshooting experience;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of projects")," forces us to centralize scattered configs into flexible organization presets;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of tests")," prompts us to optimize the codebase and incline it towards cloud and remote execution.")),(0,a.yg)("p",null,"Our core team has been facing challenges of limited human resource constraints and growing scaling needs for a long time. In many ways, that has shaped a specific mindset within the team. We evaluate every discussed feature by asking a simple question: ",(0,a.yg)("em",{parentName:"p"},"will it save other people and us time to focus on more important things?")," Teaching a man to fish is better than giving fish, so our success at preventing support issues matters more than our success at solving them ourselves."),(0,a.yg)("p",null,"That's why we'll be making subsequent efforts in these three areas, hoping to get back to you soon with even more exciting updates."),(0,a.yg)("p",null,"Enjoy your drive with Detox 20!"),(0,a.yg)("p",null,"Cheers! \ud83d\udc4b"),(0,a.yg)("div",{className:"footnotes"},(0,a.yg)("hr",{parentName:"div"}),(0,a.yg)("ol",{parentName:"div"},(0,a.yg)("li",{parentName:"ol",id:"fn-1-2b8cfe"},"The mentioned threshold is not a hard limit, but rather a point where the return value of scaling up the number of devices starts dramatically diminishing in our case \u2013 not only the tests themselves, but installing NPM dependencies and building the projects also takes time.",(0,a.yg)("a",{parentName:"li",href:"#fnref-1-2b8cfe",className:"footnote-backref"},"\u21a9")))))}d.isMDXComponent=!0},72716:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-logger-options-be0263e8b33e617b2a4ef55861d45e4a.png"},27709:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-perfetto-example-171220190ff552d655a9a0712e2c04ed.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[9034],{15680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>c});var o=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=o.createContext({}),p=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},u=function(e){var t=p(e.components);return o.createElement(l.Provider,{value:t},e.children)},g="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},m=o.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),g=p(n),m=a,c=g["".concat(l,".").concat(m)]||g[m]||d[m]||i;return n?o.createElement(c,r(r({ref:t},u),{},{components:n})):o.createElement(c,r({ref:t},u))}));function c(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,r=new Array(i);r[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[g]="string"==typeof e?e:a,r[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var o=n(58168),a=(n(96540),n(15680));const i={authors:["noomorph"],tags:["major-release","genymotion"]},r="Detox 20 is out",s={permalink:"/Detox/blog/2022/11/10/detox-20-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2022-11-10-detox-20-is-out.md",source:"@site/blog/2022-11-10-detox-20-is-out.md",title:"Detox 20 is out",description:'Today we\'re proud to announce the new major release, Detox 20 (codename "Ash\xe1n"), which brings:',date:"2022-11-10T00:00:00.000Z",formattedDate:"November 10, 2022",tags:[{label:"major-release",permalink:"/Detox/blog/tags/major-release"},{label:"genymotion",permalink:"/Detox/blog/tags/genymotion"}],readingTime:9.705,hasTruncateMarker:!1,authors:[{name:"Yaroslav Serhieiev",title:"Detox Core Contributor",url:"https://github.com/noomorph",imageURL:"https://github.com/noomorph.png",key:"noomorph"}],frontMatter:{authors:["noomorph"],tags:["major-release","genymotion"]},prevItem:{title:"Introducing Detox Copilot",permalink:"/Detox/blog/2024/10/09/detox-copilot-is-out"}},l={authorsImageUrls:[void 0]},p=[{value:"Genymotion SaaS",id:"genymotion-saas",level:2},{value:"Integration with test runners",id:"integration-with-test-runners",level:2},{value:"Configurable logger",id:"configurable-logger",level:2},{value:"Minor features",id:"minor-features",level:2},{value:"Headless iOS",id:"headless-ios",level:3},{value:"Reverse ports",id:"reverse-ports",level:3},{value:"Read-only emulators by default",id:"read-only-emulators-by-default",level:3},{value:"Reset lock file",id:"reset-lock-file",level:3},{value:"Deprecations",id:"deprecations",level:2},{value:"Afterword",id:"afterword",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...i}=e;return(0,a.yg)(g,(0,o.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"Today we're proud to announce the new major release, ",(0,a.yg)("strong",{parentName:"p"},"Detox 20"),' (codename "Ash\xe1n"), which brings:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"official support for Genymotion SaaS"),(0,a.yg)("li",{parentName:"ul"},"improved integration with test runners"),(0,a.yg)("li",{parentName:"ul"},"configurable logging subsystem"),(0,a.yg)("li",{parentName:"ul"},"headless mode for iOS via configs and CLI"),(0,a.yg)("li",{parentName:"ul"},"reversing TCP ports via Android app configs"),(0,a.yg)("li",{parentName:"ul"},"and more optimizations to land in the next minor versions.")),(0,a.yg)("h2",{id:"genymotion-saas"},"Genymotion SaaS"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/genymotion-saas"},(0,a.yg)("inlineCode",{parentName:"a"},"Using Genymotion SaaS")),"."),(0,a.yg)("p",null,"Two years ago we ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/2446"},"added elementary support")," for cloud-based Android emulators provided by ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," and started a beta testing phase across mobile projects at Wix."),(0,a.yg)("p",null,"Previously our mobile infrastructure engineers had been maintaining Android virtual devices on CI build agents on their own, so switching to cloud devices cleared up their time for more productive tasks. Another improvement was particularly noticeable for teams with a vast number of tests. We could reduce the duration of their CI pipelines almost by half after they scaled up from 2 parallel devices to 6",(0,a.yg)("sup",{parentName:"p",id:"fnref-1-2b8cfe"},(0,a.yg)("a",{parentName:"sup",href:"#fn-1-2b8cfe",className:"footnote-ref"},"1")),"."),(0,a.yg)("p",null,"This positive impact encouraged us to adopt ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," for CI as quickly as possible, ignoring some unresolved issues in the initial pull request. For the most part, those were minor problems in global lifecycle management. Yet that made us feel uncertain about releasing it as-is, so we decided to take time and gain more production experience before taking any direction."),(0,a.yg)("p",null,"The further experience was surprisingly smooth and rarely presented issues, spare for a few minor ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3573"},"glitches")," in advanced scenarios. Admittedly, revamping the ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Detox lifecycle")," took us longer than expected, which is all the more reason for us to celebrate today."),(0,a.yg)("p",null,"We're looking forward to providing our users with more opportunities for testing in the cloud, and this step is only the first of many to come. We hope you'll utilize this new feature to your delight."),(0,a.yg)("h2",{id:"integration-with-test-runners"},"Integration with test runners"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Test runner")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},(0,a.yg)("inlineCode",{parentName:"a"},"Internals API")),", ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3193"},(0,a.yg)("inlineCode",{parentName:"a"},"Dropping Mocha support")),"."),(0,a.yg)("p",null,"It took about a few months of work to formalize the contract between Detox and a test runner. While there's still a lot of place for improvement, the new Detox release refines their interaction and lays the groundwork for third-party integrations."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://mochajs.org"},"Mocha")," was our first supported test runner, but unfortunately, it could not keep up with our scaling requirements as the number of end-to-end tests grew. ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/mochajs/mocha/releases/tag/v8.0.0"},"By the time")," it acquired the ability to run tests in parallel, we already had ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/609"},"to place bets")," on another horse, and that was ",(0,a.yg)("a",{parentName:"p",href:"https://jestjs.io"},"Jest"),"."),(0,a.yg)("p",null,"We attempted to keep compatibility with both Jest and Mocha, but the farther we went, the more obvious it was that we couldn't have it both ways. As it turned out, Jest wasn't easy to get along with \u2013 our first integration with it was too simplistic. Over a couple of years of use in production, we kept discovering various issues that forced us to rewrite our \"glue\" code from scratch twice, and this isn't over yet. All combined didn't leave much time and energy for tinkering with Mocha anymore."),(0,a.yg)("p",null,"In this release, we discontinued Mocha support to focus on the attunement of Jest with the new runner-independent ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},"test runner config")," and ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Internals API"),". If there's enough demand, now it is up to the open-source community to build a new integration between Detox and Mocha."),(0,a.yg)("h2",{id:"configurable-logger"},"Configurable logger"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Logger")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Logger API")),"."),(0,a.yg)("p",null,"The rigidity of the logging subsystem has always been showing itself ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/835"},"since its very creation")," in the summer of 2019.\nDue to time constraints and existing tech debts, it was impossible to do it right the first time, so we lived about three years with a proof-of-concept rather than a full-fledged feature."),(0,a.yg)("p",null,"The inconveniences weren't fatal but quite noticeable, nevertheless. Here are a few syndromes you could have spotted if you have ever used Detox timeline and log artifacts, especially when running tests in parallel:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"an uncanny file array: ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log.json"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7506.log"),";"),(0,a.yg)("li",{parentName:"ul"},"a relatively shallow ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json"),": test suites, test functions, and some user-defined segments.")),(0,a.yg)("p",null,"The good news is that the new Detox release condenses all those numerous logs into two files:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"the plain, human-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.log"),";"),(0,a.yg)("li",{parentName:"ul"},"the raw, machine-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json")," for ",(0,a.yg)("inlineCode",{parentName:"li"},"chrome://trace"),", ",(0,a.yg)("a",{parentName:"li",href:"https://ui.perfetto.dev/"},"Perfetto")," and other utilities.")),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"A screenshot of timeline view generated by Perfetto",src:n(27709).A,width:"1600",height:"1000"})),(0,a.yg)("p",null,"With the help of the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},"Logger API"),", you can add custom duration events to the timeline, too, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"await detox.log.trace.complete('Login', async () => {\n await element(by.id('email')).typeText('john@example.com');\n await element(by.id('password')).typeText('123456');\n\n detox.log.info('Trying to log in...');\n await element(by.id('submit')).tap();\n});\n")),(0,a.yg)("p",null,"Besides, it is possible now to customize the console output of Detox via the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},"logger config"),", e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox.config.js"',title:'"detox.config.js"'},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n logger: {\n options: {\n showDate: false,\n showLoggerName: false,\n showPid: false,\n prefixers: {\n ph: null,\n },\n },\n },\n};\n")),(0,a.yg)("p",null,"In the example above, we minimize all the metadata around the log messages \u2013 see the screenshot below:"),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Terser logs after applying the override",src:n(72716).A,width:"1400",height:"399"})),(0,a.yg)("h2",{id:"minor-features"},"Minor features"),(0,a.yg)("h3",{id:"headless-ios"},"Headless iOS"),(0,a.yg)("p",null,"One of Detox known issues was always booting iOS simulators in a hidden mode. You could see tests running on your local simulator only if you had manually opened the Simulator app beforehand. So, we unified the ",(0,a.yg)("inlineCode",{parentName:"p"},"headless")," property for both iOS and Android, and now both the platforms visibly boot a device unless you configure it otherwise, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/* @type {Detox.DetoxConfig} */\nmodule.exports = {\n devices: {\n iphone: {\n type: 'ios.simulator',\n // highlight-next-line\n headless: process.env.CI ? true : undefined,\n device: {\n type: 'iPhone 14'\n },\n /* ... */\n }\n },\n};\n")),(0,a.yg)("p",null,"or, via CLI:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c ios.sim.release --headless\n")),(0,a.yg)("h3",{id:"reverse-ports"},"Reverse ports"),(0,a.yg)("p",null,"Your apps might try to access some ",(0,a.yg)("inlineCode",{parentName:"p"},"localhost:*")," addresses (e.g., mock servers), but this is a bit more problematic in the case of Android. The Android emulators are separate virtual devices with their own loopback network interface. In such cases, you must set up reverse port forwarding via ",(0,a.yg)("inlineCode",{parentName:"p"},"adb reverse"),"."),(0,a.yg)("p",null,"Local servers are quite a common prerequisite for apps in debug mode \u2013 one could recall React Native bundler on port 8081, Storybook server on 9009, etc. That's why we decided to add an optional config property for Android apps, ",(0,a.yg)("inlineCode",{parentName:"p"},"reversePorts"),":"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n apps: {\n 'android.debug': {\n type: 'android.apk',\n binaryPath: '...',\n reversePorts: [8081, 3000],\n },\n },\n};\n")),(0,a.yg)("p",null,"In other words, this is a convenience API that tells Detox to run ",(0,a.yg)("inlineCode",{parentName:"p"},"device.reverseTcpPort(portNumber)")," after installing the app. It should be helpful for anyone who prefers to keep such things as configs rather than as code."),(0,a.yg)("h3",{id:"read-only-emulators-by-default"},"Read-only emulators by default"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," flag appeared in ",(0,a.yg)("a",{parentName:"p",href:"https://developer.android.com/studio/releases/emulator#concurrent-avd"},"Android emulator 28.0.16"),". Detox promptly adopted it since the read-only mode allowed it to run multiple instances of a single Android virtual device (AVD) concurrently. This feature helped us to implement parallel test execution support for Android back then."),(0,a.yg)("p",null,"Being overcautious, we implemented that partially, only for cases when the user starts multiple concurrent workers. This decision created a moderately annoying UX issue. Imagine you run tests sequentially first, using one worker only. That provides you with a regular AVD instance, i.e., not a read-only one. After that, you switch to multiple workers only to get an error from the Android emulator, complaining about mixing regular and read-only instances."),(0,a.yg)("p",null,"While the fix itself has always been straightforward \u2013 close the running AVD and try again \u2013 this entire overcaution brought more issues than solving them. That's why, from now on, Android emulators will always be starting in ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," mode unless you configure ",(0,a.yg)("inlineCode",{parentName:"p"},"readonly: false")," in your ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/devices"},"device config"),"."),(0,a.yg)("h3",{id:"reset-lock-file"},"Reset lock file"),(0,a.yg)("p",null,"This release adds a small CLI tool, ",(0,a.yg)("a",{parentName:"p",href:"/docs/cli/reset-lock-file"},(0,a.yg)("inlineCode",{parentName:"a"},"detox reset-lock-file")),", to help users with one specific use scenario."),(0,a.yg)("p",null,"Imagine you want to run tests for multiple Detox configurations at once, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c iphoneSE2020.release e2e/ui.test.js\ndetox test -c iphone14ProMax.release e2e/ui.test.js\n")),(0,a.yg)("p",null,"The problem is that Detox uses a file-locking mechanism to avoid situations when parallel test workers would take control of the same device. The ",(0,a.yg)("inlineCode",{parentName:"p"},"detox test")," command, upon start, erases that file contents, creating a race condition risk."),(0,a.yg)("p",null,"To eliminate that risk, use a combination of ",(0,a.yg)("inlineCode",{parentName:"p"},"detox reset-lock-file")," and ",(0,a.yg)("inlineCode",{parentName:"p"},"--keepLockFile")," like this:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox reset-lock-file & \\\ndetox test --keepLockFile -c iphoneSE2020.release e2e/ui.test.js & \\\ndetox test --keepLockFile -c iphone14ProMax.release e2e/ui.test.js & \\\nwait\n")),(0,a.yg)("p",null,"In the future, we plan to minimize using lock files so that you don't have to think about this low-level implementation detail.\nSo, this tool adds some convenience until we provide a next-gen solution."),(0,a.yg)("h2",{id:"deprecations"},"Deprecations"),(0,a.yg)("p",null,"Detox 20 executes many pending deprecations, so make sure to check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/migration#200"},"Migration Guide")," before upgrading:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Node.js version is ",(0,a.yg)("inlineCode",{parentName:"li"},"14.x"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Jest version is ",(0,a.yg)("inlineCode",{parentName:"li"},"27.2.5"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: Mocha test runner is no longer supported;"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued old adapters for Jest (",(0,a.yg)("inlineCode",{parentName:"li"},"jest-jasmine"),", first generation of ",(0,a.yg)("inlineCode",{parentName:"li"},"jest-circus")," adapter);"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"{ permanent: true }")," option in ",(0,a.yg)("inlineCode",{parentName:"li"},"device.appLaunchArgs.*")," methods (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3360"},"#3360"),");"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped ",(0,a.yg)("inlineCode",{parentName:"li"},"-w, --workers")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"-o, --runner-config")," args \u2013 see a ",(0,a.yg)("a",{parentName:"li",href:"/docs/guide/migration#updating-command-line-scripts"},"dedicated section")," in the migration guide;"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"--device-launch-args")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3665"},"#3665"),");"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued kebab-case properties: ",(0,a.yg)("inlineCode",{parentName:"li"},"test-runner"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"runner-config")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3371"},"#3371"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"skipLegacyWorkersInjections")," property (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3286"},"(#3286)"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"specs")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"runnerConfig")," properties"),(0,a.yg)("li",{parentName:"ul"},"Config: changed ",(0,a.yg)("a",{parentName:"li",href:"/docs/config/testRunner"},"semantics")," of ",(0,a.yg)("inlineCode",{parentName:"li"},"testRunner")," property"),(0,a.yg)("li",{parentName:"ul"},"Config: dropped support for all-in-one configurations (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3386"},"#3386"),");"),(0,a.yg)("li",{parentName:"ul"},"Android: remove deprecated native IdlePolicyConfig (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3332/files"},"#3332"),")"),(0,a.yg)("li",{parentName:"ul"},"iOS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"ios.none")," device type \u2013 see the new way to ",(0,a.yg)("a",{parentName:"li",href:"/docs/introduction/debugging#native-application-code"},"debug native code")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3361"},"#3361"),")")),(0,a.yg)("h2",{id:"afterword"},"Afterword"),(0,a.yg)("p",null,"Over the last year and a half, we have established a centralized configuration system for more than 50 projects using Detox at Wix. While it never seemed to be a cakewalk, the entire experience of troubleshooting over a hundred issues across the organization did not leave us unchanged."),(0,a.yg)("p",null,"We see numerous things to improve in Detox, but most of them boil down to the same thing \u2013 ",(0,a.yg)("strong",{parentName:"p"},"scaling"),'. Surprisingly, "scaling" makes an excellent umbrella term for nearly every challenge we\'ve been encountering lately:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of users")," requires us to improve the onboarding and troubleshooting experience;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of projects")," forces us to centralize scattered configs into flexible organization presets;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of tests")," prompts us to optimize the codebase and incline it towards cloud and remote execution.")),(0,a.yg)("p",null,"Our core team has been facing challenges of limited human resource constraints and growing scaling needs for a long time. In many ways, that has shaped a specific mindset within the team. We evaluate every discussed feature by asking a simple question: ",(0,a.yg)("em",{parentName:"p"},"will it save other people and us time to focus on more important things?")," Teaching a man to fish is better than giving fish, so our success at preventing support issues matters more than our success at solving them ourselves."),(0,a.yg)("p",null,"That's why we'll be making subsequent efforts in these three areas, hoping to get back to you soon with even more exciting updates."),(0,a.yg)("p",null,"Enjoy your drive with Detox 20!"),(0,a.yg)("p",null,"Cheers! \ud83d\udc4b"),(0,a.yg)("div",{className:"footnotes"},(0,a.yg)("hr",{parentName:"div"}),(0,a.yg)("ol",{parentName:"div"},(0,a.yg)("li",{parentName:"ol",id:"fn-1-2b8cfe"},"The mentioned threshold is not a hard limit, but rather a point where the return value of scaling up the number of devices starts dramatically diminishing in our case \u2013 not only the tests themselves, but installing NPM dependencies and building the projects also takes time.",(0,a.yg)("a",{parentName:"li",href:"#fnref-1-2b8cfe",className:"footnote-backref"},"\u21a9")))))}d.isMDXComponent=!0},72716:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-logger-options-be0263e8b33e617b2a4ef55861d45e4a.png"},27709:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-perfetto-example-171220190ff552d655a9a0712e2c04ed.png"}}]); \ No newline at end of file diff --git a/assets/js/9a34b858.03d5970a.js b/assets/js/9a34b858.03d5970a.js new file mode 100644 index 0000000000..d2cfe1a5ad --- /dev/null +++ b/assets/js/9a34b858.03d5970a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[1533],{15680:(e,t,n)=>{n.d(t,{xA:()=>c,yg:()=>m});var a=n(96540);function r(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 i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},g="mdxType",u={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,r=e.mdxType,o=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),g=p(n),d=r,m=g["".concat(l,".").concat(d)]||g[d]||u[d]||o;return n?a.createElement(m,i(i({ref:t},c),{},{components:n})):a.createElement(m,i({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[g]="string"==typeof e?e:r,i[1]=s;for(var p=2;p{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),s=n(56347),l=n(57485),p=n(31682),c=n(89466);function g(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.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:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function u(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??g(n);return function(e){const t=(0,p.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,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,s.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)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 n??null}({queryString:t,groupId:n});return[(0,l.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=u(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[l,p]=m({queryString:n,groupId:a}),[g,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),y=(()=>{const e=l??g;return d({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{y&&s(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);s(e),p(e),h(e)}),[p,h,o]),tabValues:o}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function N(e){let{className:t,block:n,selectedValue:s,selectValue:l,tabValues:p}=e;const c=[],{blockElementScrollPositionUntilNextRender:g}=(0,i.a_)(),u=e=>{const t=e.currentTarget,n=c.indexOf(t),a=p[n].value;a!==s&&(g(t),l(a))},d=e=>{let t=null;switch(e.key){case"Enter":u(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:u},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function b(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},r.createElement(N,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(b,(0,a.A)({key:String(t)},e))}},94836:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>x,contentTitle:()=>b,default:()=>D,frontMatter:()=>v,metadata:()=>w,toc:()=>C});var a=n(58168),r=(n(96540),n(15680)),o=n(11470),i=n(19365);const s={toc:[]},l="wrapper";function p(e){let{components:t,...o}=e;return(0,r.yg)(l,(0,a.A)({},s,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(82373).A,width:"1190",height:"831"})))}p.isMDXComponent=!0;const c={toc:[]},g="wrapper";function u(e){let{components:t,...o}=e;return(0,r.yg)(g,(0,a.A)({},c,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(82415).A,width:"962",height:"682"})))}u.isMDXComponent=!0;const d={toc:[]},m="wrapper";function h(e){let{components:t,...o}=e;return(0,r.yg)(m,(0,a.A)({},d,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(50765).A,width:"842",height:"743"})))}h.isMDXComponent=!0;const y={toc:[]},f="wrapper";function N(e){let{components:t,...o}=e;return(0,r.yg)(f,(0,a.A)({},y,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(85287).A,width:"598",height:"565"})))}N.isMDXComponent=!0;const v={},b="Internals API",w={unversionedId:"api/internals",id:"version-20.x/api/internals",title:"Internals API",description:"This section might be more volatile than the other ones, yet we'll do our best to adhere to Semantic Release standards even here.",source:"@site/versioned_docs/version-20.x/api/internals.mdx",sourceDirName:"api",slug:"/api/internals",permalink:"/Detox/docs/api/internals",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/api/internals.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"apiSidebar",previous:{title:"Detox Copilot",permalink:"/Detox/docs/api/copilot"},next:{title:"Design Principles",permalink:"/Detox/docs/articles/design-principles"}},x={},C=[{value:"Lifecycle",id:"lifecycle",level:2},{value:"Jest lifecycle",id:"jest-lifecycle",level:3},{value:"Detox lifecycle",id:"detox-lifecycle",level:3},{value:"Methods",id:"methods",level:2},{value:"resolveConfig([options]) [Promise<RuntimeConfig>]",id:"resolveconfigoptions-promiseruntimeconfig",level:3},{value:"getStatus() [enum]",id:"getstatus-enum",level:3},{value:"init([options]) [Promise]",id:"initoptions-promise",level:3},{value:"installWorker([options]) [Promise]",id:"installworkeroptions-promise",level:3},{value:"uninstallWorker() [Promise]",id:"uninstallworker-promise",level:3},{value:"cleanup() [Promise]",id:"cleanup-promise",level:3},{value:"Optional lifecycle",id:"optional-lifecycle",level:2},{value:"reportTestResults(array) [Promise]",id:"reporttestresultsarray-promise",level:3},{value:"onRunDescribeStart(event) [Promise]",id:"onrundescribestartevent-promise",level:3},{value:"onTestStart(event) [Promise]",id:"onteststartevent-promise",level:3},{value:"onHookFailure(event) [Promise]",id:"onhookfailureevent-promise",level:3},{value:"onTestFnFailure(event) [Promise]",id:"ontestfnfailureevent-promise",level:3},{value:"onTestDone(event) [Promise]",id:"ontestdoneevent-promise",level:3},{value:"onRunDescribeFinish(event) [Promise]",id:"onrundescribefinishevent-promise",level:3},{value:"Properties",id:"properties",level:2},{value:"config [RuntimeConfig]",id:"config-runtimeconfig",level:3},{value:"session [SessionState]",id:"session-sessionstate",level:3},{value:"log [Logger]",id:"log-logger",level:3},{value:"tracing",id:"tracing",level:3},{value:"tracing.createEventStream()",id:"tracingcreateeventstream",level:4},{value:"worker [object]",id:"worker-object",level:3}],k={toc:C},j="wrapper";function D(e){let{components:t,...s}=e;return(0,r.yg)(j,(0,a.A)({},k,s,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"internals-api"},"Internals API"),(0,r.yg)("admonition",{type:"caution"},(0,r.yg)("p",{parentName:"admonition"},"This section might be more volatile than the other ones, yet we'll do our best to adhere to Semantic Release standards even here.")),(0,r.yg)("p",null,"Detox Internals might be useful for developing advanced enterprise presets or when you are planning to integrate with a third-party test runner like ",(0,r.yg)("a",{parentName:"p",href:"https://mochajs.org"},"Mocha"),", ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/avajs/ava"},"Ava"),", ",(0,r.yg)("a",{parentName:"p",href:"https://vitest.dev"},"Vitest")," or other ones."),(0,r.yg)("h2",{id:"lifecycle"},"Lifecycle"),(0,r.yg)("p",null,"The purpose of ",(0,r.yg)("strong",{parentName:"p"},"Internals API")," is mostly to align the lifecycles of Detox and a test runner underneath.\nAlthough it is generic enough, there is no denying that its design has been influenced by our official integration with Jest test runner.\nThat's why it might be better to start the overview from Jest lifecycle first, and then move on to Detox lifecycle and how they fit together."),(0,r.yg)("h3",{id:"jest-lifecycle"},"Jest lifecycle"),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(71198).A,width:"717",height:"594"})),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"Jest's main process starts from resolving and evaluating its config file, e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"jest.config.js"),":"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"module.exports = async function () {\n return {\n globalSetup: '/path/to/globalSetup.js',\n globalTeardown: '/path/to/globalTeardown.js',\n reporters: ['/path/to/reporter.js'],\n /* ... jest config ... */\n };\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"If a ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalsetup-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalSetup"))," handler is defined, it is resolved and executed in the main process:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"module.exports = async function () {\n // ... global setup code ...\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"The next come ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#reporters-arraymodulename--modulename-options"},(0,r.yg)("inlineCode",{parentName:"a"},"reporters")),", one of the longest-living entities in the test session. After instantiating reporters, Jest calls their ",(0,r.yg)("inlineCode",{parentName:"p"},"onRunStart")," method:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"class Reporter {\n async onRunStart(aggregatedResults, options) {\n // ... reporter code ...\n }\n\n // ...\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"If Jest is not running in band (see ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/cli#--runinband"},(0,r.yg)("inlineCode",{parentName:"a"},"-i, --runInBand")),"), and if it has ",(0,r.yg)("em",{parentName:"p"},"N")," ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#maxworkers-number--string"},"workers")," (",(0,r.yg)("em",{parentName:"p"},"N > 1"),"), then it spawns ",(0,r.yg)("em",{parentName:"p"},"N")," child processes that keep taking test files one after another, running their tests inside and reporting back to the reporters:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"class Reporter {\n async onTestFileResult(test, testResult, aggregatedResult) {\n // ... reporter code ...\n }\n\n // ...\n};\n")),(0,r.yg)("p",{parentName:"li"},"Otherwise, Jest runs all the tests in the main process without spawning any other processes, but the reporting flow itself remains the same, as can be seen in the diagram:"),(0,r.yg)("p",{parentName:"li"},(0,r.yg)("img",{alt:"UML sequence diagram",src:n(47962).A,width:"591",height:"616"}))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"After all the tests have been executed, Jest calls ",(0,r.yg)("inlineCode",{parentName:"p"},"onRunComplete")," in the reporters, and this is the final phase for any reporter:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"class Reporter {\n async onRunComplete(testContexts, results) {\n // ... reporter code ...\n }\n\n // ...\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"The last user-controlled hook is the global teardown. If a ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalteardown-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalTeardown"))," handler is defined, it is resolved and executed in the main process:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"module.exports = async function () {\n // ... global teardown code ...\n};\n")))),(0,r.yg)("h3",{id:"detox-lifecycle"},"Detox lifecycle"),(0,r.yg)("p",null,"Theoretically, Detox CLI could be totally agnostic about the test runner under the hood, but that would deprive us of some convenience.\nFor instance, there is a ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/testRunner#testrunnerretries-number"},"retry mechanism"),", built into Detox CLI, which can schedule extra runs for failed test files.\nThis requirement obliges Detox context to live longer than any test runner, and requires from the test runner to be able to report back to Detox CLI, whereas the resulting child process hierarchy can be broad and multi-tiered, e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"\u2514\u2500\u2500 detox test ...\n \u2514\u2500\u2500 jest ... --maxWorkers N\n \u251c\u2500\u2500 jest-worker (1)\n \u251c\u2500\u2500 ...\n \u2514\u2500\u2500 jest-worker (N)\n")),(0,r.yg)("p",null,"Even if we run Jest directly, without Detox CLI, there's still a one-to-many relationship between its processes:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"\u2514\u2500\u2500 jest ... --maxWorkers N\n \u251c\u2500\u2500 jest-worker (1)\n \u251c\u2500\u2500 ...\n \u2514\u2500\u2500 jest-worker (N)\n")),(0,r.yg)("p",null,"So, if we want to be on the safe side, every process should be able to communicate with the root process, where we have the primary context of Detox, and vice versa.\nRetrying failed tests is just one of numerous needs, and there are more:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the primary context (and workers themselves, at times) needs to know how many workers are there;"),(0,r.yg)("li",{parentName:"ul"},"the workers should request from the primary context to allocate a device and return it back when they are done;"),(0,r.yg)("li",{parentName:"ul"},"any secondary context should be able to tell whether this is a first time it is running, or it is an ",(0,r.yg)("em",{parentName:"li"},"N"),"-th attempt already;")),(0,r.yg)("p",null,"This list can be continued and might expand even more with time, but the point is that Detox contexts will get instantiated as many times as child processes are created during the test session, and it should be something trivial to synchronize the primary and the secondary contexts to ensure a seamless experience."),(0,r.yg)("p",null,'It is worth mentioning that Jest\'s main process is ill-suited for taking a device and running the tests, as its purpose is to orchestrate the entire test session and not run the tests themselves.\nThis means that not all secondary contexts of Detox are "born equal" \u2013 most of them will be allocating a device for running tests, but at least one will be merely communicating with the primary context.'),(0,r.yg)("p",null,"This is exactly the reason why we call ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," in every child process, but sometimes it is not just a simple call, but an ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init({ workerId: null })"))," override to avoid creating a worker.\nHowever, the fact of initializing without a worker does not mean we can't call ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()"))," later. For example, if it turns out that Jest is running in a single process, then instead of creating two Detox contexts within the same process, we're going to reuse the existing one and just supplement it with the worker itself."),(0,r.yg)("p",null,"There's one more implicit thing that happens at the very beginning of ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init([options])"))," method, and that is the config resolution. It is also available as a separate method, ",(0,r.yg)("a",{parentName:"p",href:"#resolveconfigoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"resolveConfig([options])"))," based on the following considerations.\nWhen you use a test runner directly, without Detox CLI (e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"jest \u2026")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"detox test \u2026"),"), then the test runner config gets resolved earlier than Detox config itself. That creates a dangerous scenario for dynamically generated configs:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="jest.config.js"',title:'"jest.config.js"'},"const { config } = require('detox/internals');\n\nmodule.exports = async () => {\n if (config.device.type === 'ios.simulator') {\n return { /* iOS-specific preset */ };\n } else {\n return { /* Android-specific preset */ };\n }\n};\n")),(0,r.yg)("p",null,"The problem in this case is that we are accessing an unresolved (yet) config. Of course, one could assume that it is possible to overcome with a plain ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," call like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="bad-idea.config.js"',title:'"bad-idea.config.js"'},"const { init, config } = require('detox/internals');\n\nmodule.exports = async () => {\n await init();\n\n if (config.device.type === 'ios.simulator') {\n // ... use config now ...\n }\n};\n")),(0,r.yg)("p",null,"This solution will work, but it is rather a bad one, since Jest config resolution is an asymmetrical step compared to ",(0,r.yg)("inlineCode",{parentName:"p"},"globalSetup")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"globalTeardown"),". While solving one problem, it creates another one. Consider running this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-sh"},"jest --config bad-idea.config.js --showConfig\n")),(0,r.yg)("p",null,"For reference, when Jest runs with a ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/cli#--showconfig"},(0,r.yg)("inlineCode",{parentName:"a"},"--showConfig"))," option, all it does is ",(0,r.yg)("em",{parentName:"p"},"to resolve the config and to print it"),". Hence, neither ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalsetup-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalSetup"))," nor ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalteardown-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalTeardown"))," will be called, and the test runner will hang up since there's no one to call the ",(0,r.yg)("a",{parentName:"p",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup()"))," method which stops the ",(0,r.yg)("a",{parentName:"p",href:"https://www.npmjs.com/package/node-ipc"},"IPC server")," used for the communication between the primary and the secondary contexts."),(0,r.yg)("p",null,"Still, we have to be able to access the config early, and that is exactly why ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," method is a composite of ",(0,r.yg)("a",{parentName:"p",href:"#resolveconfigoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"resolveConfig()")),", the ",(0,r.yg)("em",{parentName:"p"},"actual")," init, and ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()")),". We could describe what it does on the high level with the following pseudocode:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"async function init(options = {}) {\n config = config || await resolveConfig();\n\n await logger.init(config);\n await ipcServer.init(config);\n // ... init more things ...\n\n if (options.workerId != null) {\n await installWorker();\n }\n}\n")),(0,r.yg)("p",null,"In other words, it will resolve config only if it has not been resolved before, and install a worker unless it has been forbidden explicitly.\nAnd even that we can ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()"))," later if we ever need it."),(0,r.yg)("p",null,"Now, when many details are clarified, we can review the actual sequence diagrams step by step. There are four scenarios depending on the initiator (Detox CLI or the test runner itself) and on child process hierarchy (a single process or parent-children)."),(0,r.yg)("p",null,"A few words about the diagram and its conventions:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ClassName.0"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"ClassName.1"),", \u2026, ",(0,r.yg)("inlineCode",{parentName:"li"},"ClassName.N")," suffixes mean the index of the instance of the class created."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"DetoxCircusEnvironment.N")," is our custom ",(0,r.yg)("a",{parentName:"li",href:"https://jestjs.io/docs/configuration#testenvironment-string"},(0,r.yg)("inlineCode",{parentName:"a"},"testEnvironment"))," created one or multiple times in every Jest worker. Make sure to read about Jest ",(0,r.yg)("a",{parentName:"li",href:"https://jestjs.io/docs/configuration#testenvironment-string"},"test environments")," and look at the example section there for better understanding.")),(0,r.yg)(o.A,{mdxType:"Tabs"},(0,r.yg)(i.A,{value:"detox test \u2026 --maxWorkers N",mdxType:"TabItem"},(0,r.yg)(p,{mdxType:"LifecycleManyWorkersCLI"})),(0,r.yg)(i.A,{value:"jest \u2026 --maxWorkers N",mdxType:"TabItem"},(0,r.yg)(u,{mdxType:"LifecycleManyWorkersJest"})),(0,r.yg)(i.A,{value:"detox test \u2026 --runInBand",mdxType:"TabItem"},(0,r.yg)(h,{mdxType:"LifecycleSingleWorkerCLI"})),(0,r.yg)(i.A,{value:"jest \u2026 --runInBand",mdxType:"TabItem"},(0,r.yg)(N,{mdxType:"LifecycleSingleWorkerJest"}))),(0,r.yg)("h2",{id:"methods"},"Methods"),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},"Feel free to browse through ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/blob/master/detox/internals.d.ts"},"the typings file")," provided by Detox.")),(0,r.yg)("h3",{id:"resolveconfigoptions-promiseruntimeconfig"},(0,r.yg)("inlineCode",{parentName:"h3"},"resolveConfig([options])")," ","[","Promise","]"),(0,r.yg)("p",null,"Use sparingly for cases when you need to read Detox config before ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," is called."),(0,r.yg)("p",null,"If you use Detox with Jest, our default integration, the only place where you might need it is your ",(0,r.yg)("inlineCode",{parentName:"p"},"jest.config.js"),", e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="jest.config.js"',title:'"jest.config.js"'},"const { resolveConfig } = require('detox/internals');\n\nmodule.exports = async () => {\n /** @type {DetoxInternals.RuntimeConfig} */\n const config = await resolveConfig();\n\n return { /* Jest config */ };\n};\n")),(0,r.yg)("p",null,"For example, you could use that to evaluate the ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#maxworkers-number--string"},(0,r.yg)("inlineCode",{parentName:"a"},"maxWorkers"))," count depending on the device type,\nbut please mind, though, that Detox allows to override test runner options via its own config, e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox.config.js"',title:'"detox.config.js"'},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n apps: { /* ... */ },\n devices: { /* ... */ },\n configurations: {\n 'ios.sim.release': {\n app: 'ios.release',\n device: 'iphone',\n testRunner: {\n args: {\n// highlight-next-line\n maxWorkers: process.env.CI === 'true' ? 3 : undefined,\n },\n },\n },\n 'android.emu.release': {\n app: 'android.release',\n device: 'nexus',\n testRunner: {\n args: {\n// highlight-next-line\n maxWorkers: process.env.CI === 'true' ? 2 : undefined,\n },\n },\n },\n },\n};\n")),(0,r.yg)("p",null,"This trick shown above allows to forward extra CLI arguments to ",(0,r.yg)("inlineCode",{parentName:"p"},"jest")," conditionally, e.g. when running in CI mode:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"CI=true detox test -c ios.sim.release\n# jest --config e2e/jest.config.js --maxWorkers 3\n")),(0,r.yg)("h3",{id:"getstatus-enum"},(0,r.yg)("inlineCode",{parentName:"h3"},"getStatus()")," ","[","enum]"),(0,r.yg)("p",null,"Returns one of statuses depending on what\u2019s going with the current Detox context:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"inactive")," \u2013 before ",(0,r.yg)("a",{parentName:"li",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," and after ",(0,r.yg)("a",{parentName:"li",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup()"))," is called."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"init")," \u2013 while ",(0,r.yg)("a",{parentName:"li",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," is executing."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"active")," \u2013 after ",(0,r.yg)("a",{parentName:"li",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," and before ",(0,r.yg)("a",{parentName:"li",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup()"))," is called."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"cleanup")," \u2013 while ",(0,r.yg)("a",{parentName:"li",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup"))," is executing.")),(0,r.yg)("h3",{id:"initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"init([options])")," ","[","Promise]"),(0,r.yg)("p",null,"This is the phase where:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a primary Detox context resolves its configuration, starts the logger, IPC server, and more;"),(0,r.yg)("li",{parentName:"ul"},"a secondary Detox context connects to IPC server and registers itself;"),(0,r.yg)("li",{parentName:"ul"},"if ",(0,r.yg)("inlineCode",{parentName:"li"},"workerId")," is not null, ",(0,r.yg)("a",{parentName:"li",href:"#installworkeroptions-promise"},"installs the worker"),";")),(0,r.yg)("p",null,"Accepts an optional parameter, ",(0,r.yg)("inlineCode",{parentName:"p"},"options")," object with the following properties, ",(0,r.yg)("em",{parentName:"p"},"all optional")," as well:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"cwd")," (string) \u2013 current working directory, used to ",(0,r.yg)("a",{parentName:"li",href:"/Detox/docs/config/overview#path-conventions"},"resolve Detox config"),"."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"argv")," (key-value map) \u2013 ",(0,r.yg)("strong",{parentName:"li"},"Internal!"),". CLI arguments parsed by Detox CLI."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testRunnerArgv")," (key-value map) \u2013 CLI arguments to be forwarded to the test runner."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"override")," (Partial",") \u2013 ad-hoc adjustments to deep merge with the file-based config."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"global")," \u2013 reference to a custom ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/globals.html"},"global")," scope, usually needed when your test runner ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/vm.html"},"uses sandboxing"),".\nThis prevents creating issues when a Detox context cannot be accessed from within the sandboxed environment."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"workerId")," \u2013 (string ","|"," null) a unique ID, e.g. ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-1"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-2"),". Giving ",(0,r.yg)("inlineCode",{parentName:"li"},"null")," disables installing the worker.")),(0,r.yg)("h3",{id:"installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"installWorker([options])")," ","[","Promise]"),(0,r.yg)("p",null,"This is the phase where Detox loads its expectation library and boots a device. You don't need to call it separately\nunless you use ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init({ workerId: null })"))," override."),(0,r.yg)("p",null,"Accepts an optional parameter, ",(0,r.yg)("inlineCode",{parentName:"p"},"options")," object with the following properties, ",(0,r.yg)("em",{parentName:"p"},"all optional")," as well:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"global")," \u2013 reference to a custom ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/globals.html"},"global")," scope, usually needed when your test runner ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/vm.html"},"uses sandboxing"),".\nThis prevents creating issues when a Detox context cannot be accessed from within the sandboxed environment."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"workerId")," \u2013 (string ","|"," null) a unique ID, e.g. ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-1"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-2"),". Giving ",(0,r.yg)("inlineCode",{parentName:"li"},"null")," disables installing the worker.")),(0,r.yg)("h3",{id:"uninstallworker-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"uninstallWorker()")," ","[","Promise]"),(0,r.yg)("p",null,"Deallocates the device. Most Client API (",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/device"},(0,r.yg)("inlineCode",{parentName:"a"},"device")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},(0,r.yg)("inlineCode",{parentName:"a"},"by")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions"},(0,r.yg)("inlineCode",{parentName:"a"},"element")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect"},(0,r.yg)("inlineCode",{parentName:"a"},"expect")),") will stop working, except for ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/logger"},"the logger"),"."),(0,r.yg)("h3",{id:"cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"cleanup()")," ","[","Promise]"),(0,r.yg)("p",null,"This method should be called when the main or child process is about to exit. Under the hood, it:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"#uninstallworker-promise"},"uninstalls the worker")," if there is a worker installed;"),(0,r.yg)("li",{parentName:"ul"},"a secondary Detox context disconnects from the ",(0,r.yg)("a",{parentName:"li",href:"https://www.npmjs.com/package/node-ipc"},"IPC server"),";"),(0,r.yg)("li",{parentName:"ul"},"a primary Detox context stops the IPC server and collects the log artifacts.")),(0,r.yg)("h2",{id:"optional-lifecycle"},"Optional lifecycle"),(0,r.yg)("h3",{id:"reporttestresultsarray-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"reportTestResults(array)")," ","[","Promise]"),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"},"This method has an effect only when the tests are run via ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test"},"Detox CLI"),".")),(0,r.yg)("p",null,"Reports to the primary context about failed tests that could have been re-run if ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test#options"},(0,r.yg)("inlineCode",{parentName:"a"},"-R, --retries"))," mechanism is enabled."),(0,r.yg)("p",null,"It takes one argument, an array of test file reports. Each report is an object with the following properties:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testFilePath")," (string) \u2014 global or relative path to the failed test file;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"success")," (boolean) \u2014 whether the test passed or not;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testExecError")," (optional error) \u2014 top-level error, use it only if the entire test file failed, e.g. due to a syntax error or environment setup failure;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"isPermanentFailure")," (optional boolean) \u2014 if the test failed, it should tell whether the failure is permanent. Permanent failure means that the test file should not be re-run. For instance, we use it to ",(0,r.yg)("a",{parentName:"li",href:"/Detox/docs/config/testRunner#testrunnerjestretryaftercircusretries-boolean"},"prevent double retries"),": with Detox CLI and with ",(0,r.yg)("a",{parentName:"li",href:"https://jestjs.io/docs/jest-object#jestretrytimesnumretries-options"},(0,r.yg)("inlineCode",{parentName:"a"},"jest.retryTimes()")),".")),(0,r.yg)("h3",{id:"onrundescribestartevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onRunDescribeStart(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports that the test runner started executing a test suite, e.g. a ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAll")," hook or a first test:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onRunDescribeStart({\n name: 'Suite name'\n});\n")),(0,r.yg)("h3",{id:"onteststartevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onTestStart(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports that the test runner started executing a specific test. Use ",(0,r.yg)("inlineCode",{parentName:"p"},"invocations")," when a test is being re-run after a failure:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestStart({\n title: 'should do something expected',\n fullName: 'Suite name should do something expected',\n invocations: 1,\n status: 'running',\n});\n")),(0,r.yg)("h3",{id:"onhookfailureevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onHookFailure(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports about an error in the midst of ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAll"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeEach"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"afterEach"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"afterAll")," or any other hook. We use it, for example, to generate ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts"},"screenshot artifacts")," like ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAllFailure.png"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onHookFailure({\n error: new Error('Some assertion failed'),\n hook: 'beforeEach',\n});\n")),(0,r.yg)("h3",{id:"ontestfnfailureevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onTestFnFailure(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports about an error in the midst of a test function. We use it, for example, to generate ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts"},"screenshot artifacts")," like ",(0,r.yg)("inlineCode",{parentName:"p"},"testFnFailure.png"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestFnFailure({ error: new Error('Some assertion failed') });\n")),(0,r.yg)("h3",{id:"ontestdoneevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onTestDone(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports the final status of the test, ",(0,r.yg)("inlineCode",{parentName:"p"},"passed")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"failed"),", e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestDone({\n title: 'should do something expected',\n fullName: 'Suite name should do something expected',\n invocations: 1,\n status: 'failed',\n timedOut: false,\n});\n")),(0,r.yg)("p",null,"Besides collecting ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts"},"log, screenshot and video artifacts"),", this hook resets the pending network requests \u2013\nthese are usually some actions to which the app did not respond during the test. Setting ",(0,r.yg)("inlineCode",{parentName:"p"},"timedOut: true")," tells Detox to dump those pending requests, if there are any."),(0,r.yg)("p",null,"If your test runner supports re-running internally the failed tests, and, for example, your test passes on the second attempt, you would call the method with something like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestDone({\n title: 'should do something expected',\n fullName: 'Suite name should do something expected',\n invocations: 2,\n status: 'passed',\n});\n")),(0,r.yg)("h3",{id:"onrundescribefinishevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onRunDescribeFinish(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports that the test runner has finished executing a test suite, e.g. all the ",(0,r.yg)("inlineCode",{parentName:"p"},"afterAll")," hooks have been executed or the last test has finished running:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onRunDescribeFinish({ name: 'Suite name' });\n")),(0,r.yg)("h2",{id:"properties"},"Properties"),(0,r.yg)("h3",{id:"config-runtimeconfig"},(0,r.yg)("inlineCode",{parentName:"h3"},"config")," ","[","RuntimeConfig]"),(0,r.yg)("p",null,"Open ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/blob/master/detox/internals.d.ts"},"the typings file")," and search for ",(0,r.yg)("inlineCode",{parentName:"p"},"RuntimeConfig"),"."),(0,r.yg)("p",null,"For the most part, this config is identical to what we describe in ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/overview"},"Config docs"),", except that it is non-optional.\nIn other words, even if you never customized your ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/session"},"Session config"),", you'll still be able to access some default\nvalues safely, without null checks."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"const { config } = require('detox/internals');\ntypeof config.session.autoStart // \"boolean\"\n")),(0,r.yg)("h3",{id:"session-sessionstate"},(0,r.yg)("inlineCode",{parentName:"h3"},"session")," ","[","SessionState]"),(0,r.yg)("p",null,"The session state contains the following read-only properties:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"id")," (string) \u2013 randomly generated ID for the entire Detox test session, including retries."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testResults")," (DetoxTestFileReport[]) \u2013 results of the prior test file executions, used by Detox CLI retry mechanism."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testSessionIndex")," (number) \u2013 the retry index of the test session: ",(0,r.yg)("inlineCode",{parentName:"li"},"0.."),"."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"workersCount")," (number) \u2013 count of Detox contexts with a worker installed.\nIf we oversimplify it, it reflects the count of allocated devices in the current test session.")),(0,r.yg)("p",null,"The session state, including the resolved config, is serialized by the primary context, so that the secondary Detox contexts can read it synchronously from a file at the earliest point possible.\nAfter the secondary contexts connect to the IPC server hosted by the primary context, they register themselves and get the up-to-date session state.\nThe IPC server broadcasts the updates to all the connected contexts on every action like ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()"))," or ",(0,r.yg)("a",{parentName:"p",href:"#reporttestresultsarray-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"reportTestResults()")),"."),(0,r.yg)("h3",{id:"log-logger"},(0,r.yg)("inlineCode",{parentName:"h3"},"log")," ","[","Logger]"),(0,r.yg)("p",null,"See ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/logger"},"Logger API")," for all the details."),(0,r.yg)("p",null,"The only difference from the Client API here is that you don't have a predefined ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," category, i.e.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"const { log: logClient } = require('detox');\nconst { log: logInternal } = require('detox/internals');\n\n// oversimplified, it looks like:\nlogClient == logInternal.child({ cat: 'user' })\n")),(0,r.yg)("p",null,"For example, we leverage this for adding more ",(0,r.yg)("inlineCode",{parentName:"p"},"lifecycle")," events in our integration with Jest:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox/runners/jest/testEnvironment/index.js"',title:'"detox/runners/jest/testEnvironment/index.js"'},"class DetoxCircusEnvironment extends NodeEnvironment {\n constructor(config, context) {\n super(/* ... */);\n log.trace.begin({ cat: 'lifecycle' }, context.testPath);\n // ...\n }\n}\n")),(0,r.yg)("h3",{id:"tracing"},(0,r.yg)("inlineCode",{parentName:"h3"},"tracing")),(0,r.yg)("p",null,"An advanced API useful for creating reports based on logged Detox events."),(0,r.yg)("h4",{id:"tracingcreateeventstream"},(0,r.yg)("inlineCode",{parentName:"h4"},"tracing.createEventStream()")),(0,r.yg)("p",null,"Creates a readable stream of the currently recorded events in\n",(0,r.yg)("a",{parentName:"p",href:"https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU"},"Chrome Trace Event format"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"const { tracing } = require('detox/internals');\n\nasync function processDetoxEvents() {\n await new Promise((resolve, reject) => {\n tracing\n .createEventStream()\n .on('end', resolve)\n .on('error', reject)\n .on('data', (event) => {\n if (event.ph === 'B') { /* duration event (begin) */ }\n if (event.ph === 'E') { /* duration event (end) */ }\n if (event.ph === 'i') { /* instant event */ }\n });\n });\n}\n")),(0,r.yg)("p",null,"Please mind that you'll be getting a snapshot of events aggregated from all the sibling and child processes, and it never will be complete until the very end of the test session."),(0,r.yg)("p",null,"See also: ",(0,r.yg)("a",{parentName:"p",href:"https://wix-incubator.github.io/trace-event-lib/interfaces/DurationBeginEvent.html"},(0,r.yg)("inlineCode",{parentName:"a"},"DurationBeginEvent")),", ",(0,r.yg)("a",{parentName:"p",href:"https://wix-incubator.github.io/trace-event-lib/interfaces/DurationEndEvent.html"},(0,r.yg)("inlineCode",{parentName:"a"},"DurationEndEvent")),", ",(0,r.yg)("a",{parentName:"p",href:"https://wix-incubator.github.io/trace-event-lib/interfaces/InstantEvent.html"},(0,r.yg)("inlineCode",{parentName:"a"},"InstantEvent")),"."),(0,r.yg)("h3",{id:"worker-object"},(0,r.yg)("inlineCode",{parentName:"h3"},"worker")," ","[","object]"),(0,r.yg)("p",null,"Not documented on purpose. Provides direct access to the object which holds the device driver, websocket client, matchers, expectations, etc."))}D.isMDXComponent=!0},82373:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-cli-many-workers-95ab2193ce10864c0742f01ffd30b58b.svg"},50765:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-cli-single-worker-6672b7356e2146d40f39f80145780949.svg"},82415:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-jest-many-workers-ce60aaacc2101a9b810d2d22fe0df51b.svg"},85287:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-jest-single-worker-278e1383ad4f31570f073fa12689aec6.svg"},47962:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/jest-diagram-runInBand-b0e7564080d4f6a0a03e4dbcf6b0da7f.svg"},71198:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/jest-diagram-d957dd3e62301816b44197ef0a4abee0.svg"}}]); \ No newline at end of file diff --git a/assets/js/9a34b858.fca19837.js b/assets/js/9a34b858.fca19837.js deleted file mode 100644 index cbdba14eb4..0000000000 --- a/assets/js/9a34b858.fca19837.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[1533],{15680:(e,t,n)=>{n.d(t,{xA:()=>g,yg:()=>m});var a=n(96540);function r(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 i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},g=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},c="mdxType",u={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,r=e.mdxType,o=e.originalType,l=e.parentName,g=s(e,["components","mdxType","originalType","parentName"]),c=p(n),d=r,m=c["".concat(l,".").concat(d)]||c[d]||u[d]||o;return n?a.createElement(m,i(i({ref:t},g),{},{components:n})):a.createElement(m,i({ref:t},g))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[c]="string"==typeof e?e:r,i[1]=s;for(var p=2;p{n.d(t,{A:()=>i});var a=n(96540),r=n(20053);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,i),hidden:n},t)}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var a=n(58168),r=n(96540),o=n(20053),i=n(23104),s=n(56347),l=n(57485),p=n(31682),g=n(89466);function c(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.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:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function u(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??c(n);return function(e){const t=(0,p.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,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,s.W6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)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 n??null}({queryString:t,groupId:n});return[(0,l.aZ)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=u(e),[i,s]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[l,p]=m({queryString:n,groupId:a}),[c,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,g.Dv)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),y=(()=>{const e=l??c;return d({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{y&&s(y)}),[y]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);s(e),p(e),h(e)}),[p,h,o]),tabValues:o}}var y=n(92303);const f={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function N(e){let{className:t,block:n,selectedValue:s,selectValue:l,tabValues:p}=e;const g=[],{blockElementScrollPositionUntilNextRender:c}=(0,i.a_)(),u=e=>{const t=e.currentTarget,n=g.indexOf(t),a=p[n].value;a!==s&&(c(t),l(a))},d=e=>{let t=null;switch(e.key){case"Enter":u(e);break;case"ArrowRight":{const n=g.indexOf(e.currentTarget)+1;t=g[n]??g[0];break}case"ArrowLeft":{const n=g.indexOf(e.currentTarget)-1;t=g[n]??g[g.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t)},p.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.A)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>g.push(e),onKeyDown:d,onClick:u},i,{className:(0,o.A)("tabs__item",f.tabItem,i?.className,{"tabs__item--active":s===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function b(e){const t=h(e);return r.createElement("div",{className:(0,o.A)("tabs-container",f.tabList)},r.createElement(N,(0,a.A)({},e,t)),r.createElement(v,(0,a.A)({},e,t)))}function w(e){const t=(0,y.A)();return r.createElement(b,(0,a.A)({key:String(t)},e))}},94836:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>x,contentTitle:()=>b,default:()=>D,frontMatter:()=>v,metadata:()=>w,toc:()=>C});var a=n(58168),r=(n(96540),n(15680)),o=n(11470),i=n(19365);const s={toc:[]},l="wrapper";function p(e){let{components:t,...o}=e;return(0,r.yg)(l,(0,a.A)({},s,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(82373).A,width:"1190",height:"831"})))}p.isMDXComponent=!0;const g={toc:[]},c="wrapper";function u(e){let{components:t,...o}=e;return(0,r.yg)(c,(0,a.A)({},g,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(82415).A,width:"962",height:"682"})))}u.isMDXComponent=!0;const d={toc:[]},m="wrapper";function h(e){let{components:t,...o}=e;return(0,r.yg)(m,(0,a.A)({},d,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(50765).A,width:"842",height:"743"})))}h.isMDXComponent=!0;const y={toc:[]},f="wrapper";function N(e){let{components:t,...o}=e;return(0,r.yg)(f,(0,a.A)({},y,o,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(85287).A,width:"598",height:"565"})))}N.isMDXComponent=!0;const v={},b="Internals API",w={unversionedId:"api/internals",id:"version-20.x/api/internals",title:"Internals API",description:"This section might be more volatile than the other ones, yet we'll do our best to adhere to Semantic Release standards even here.",source:"@site/versioned_docs/version-20.x/api/internals.mdx",sourceDirName:"api",slug:"/api/internals",permalink:"/Detox/docs/api/internals",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/api/internals.mdx",tags:[],version:"20.x",frontMatter:{},sidebar:"apiSidebar",previous:{title:"Logger",permalink:"/Detox/docs/api/logger"},next:{title:"Design Principles",permalink:"/Detox/docs/articles/design-principles"}},x={},C=[{value:"Lifecycle",id:"lifecycle",level:2},{value:"Jest lifecycle",id:"jest-lifecycle",level:3},{value:"Detox lifecycle",id:"detox-lifecycle",level:3},{value:"Methods",id:"methods",level:2},{value:"resolveConfig([options]) [Promise<RuntimeConfig>]",id:"resolveconfigoptions-promiseruntimeconfig",level:3},{value:"getStatus() [enum]",id:"getstatus-enum",level:3},{value:"init([options]) [Promise]",id:"initoptions-promise",level:3},{value:"installWorker([options]) [Promise]",id:"installworkeroptions-promise",level:3},{value:"uninstallWorker() [Promise]",id:"uninstallworker-promise",level:3},{value:"cleanup() [Promise]",id:"cleanup-promise",level:3},{value:"Optional lifecycle",id:"optional-lifecycle",level:2},{value:"reportTestResults(array) [Promise]",id:"reporttestresultsarray-promise",level:3},{value:"onRunDescribeStart(event) [Promise]",id:"onrundescribestartevent-promise",level:3},{value:"onTestStart(event) [Promise]",id:"onteststartevent-promise",level:3},{value:"onHookFailure(event) [Promise]",id:"onhookfailureevent-promise",level:3},{value:"onTestFnFailure(event) [Promise]",id:"ontestfnfailureevent-promise",level:3},{value:"onTestDone(event) [Promise]",id:"ontestdoneevent-promise",level:3},{value:"onRunDescribeFinish(event) [Promise]",id:"onrundescribefinishevent-promise",level:3},{value:"Properties",id:"properties",level:2},{value:"config [RuntimeConfig]",id:"config-runtimeconfig",level:3},{value:"session [SessionState]",id:"session-sessionstate",level:3},{value:"log [Logger]",id:"log-logger",level:3},{value:"tracing",id:"tracing",level:3},{value:"tracing.createEventStream()",id:"tracingcreateeventstream",level:4},{value:"worker [object]",id:"worker-object",level:3}],k={toc:C},j="wrapper";function D(e){let{components:t,...s}=e;return(0,r.yg)(j,(0,a.A)({},k,s,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"internals-api"},"Internals API"),(0,r.yg)("admonition",{type:"caution"},(0,r.yg)("p",{parentName:"admonition"},"This section might be more volatile than the other ones, yet we'll do our best to adhere to Semantic Release standards even here.")),(0,r.yg)("p",null,"Detox Internals might be useful for developing advanced enterprise presets or when you are planning to integrate with a third-party test runner like ",(0,r.yg)("a",{parentName:"p",href:"https://mochajs.org"},"Mocha"),", ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/avajs/ava"},"Ava"),", ",(0,r.yg)("a",{parentName:"p",href:"https://vitest.dev"},"Vitest")," or other ones."),(0,r.yg)("h2",{id:"lifecycle"},"Lifecycle"),(0,r.yg)("p",null,"The purpose of ",(0,r.yg)("strong",{parentName:"p"},"Internals API")," is mostly to align the lifecycles of Detox and a test runner underneath.\nAlthough it is generic enough, there is no denying that its design has been influenced by our official integration with Jest test runner.\nThat's why it might be better to start the overview from Jest lifecycle first, and then move on to Detox lifecycle and how they fit together."),(0,r.yg)("h3",{id:"jest-lifecycle"},"Jest lifecycle"),(0,r.yg)("p",null,(0,r.yg)("img",{alt:"UML sequence diagram",src:n(71198).A,width:"717",height:"594"})),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"Jest's main process starts from resolving and evaluating its config file, e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"jest.config.js"),":"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"module.exports = async function () {\n return {\n globalSetup: '/path/to/globalSetup.js',\n globalTeardown: '/path/to/globalTeardown.js',\n reporters: ['/path/to/reporter.js'],\n /* ... jest config ... */\n };\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"If a ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalsetup-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalSetup"))," handler is defined, it is resolved and executed in the main process:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"module.exports = async function () {\n // ... global setup code ...\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"The next come ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#reporters-arraymodulename--modulename-options"},(0,r.yg)("inlineCode",{parentName:"a"},"reporters")),", one of the longest-living entities in the test session. After instantiating reporters, Jest calls their ",(0,r.yg)("inlineCode",{parentName:"p"},"onRunStart")," method:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"class Reporter {\n async onRunStart(aggregatedResults, options) {\n // ... reporter code ...\n }\n\n // ...\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"If Jest is not running in band (see ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/cli#--runinband"},(0,r.yg)("inlineCode",{parentName:"a"},"-i, --runInBand")),"), and if it has ",(0,r.yg)("em",{parentName:"p"},"N")," ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#maxworkers-number--string"},"workers")," (",(0,r.yg)("em",{parentName:"p"},"N > 1"),"), then it spawns ",(0,r.yg)("em",{parentName:"p"},"N")," child processes that keep taking test files one after another, running their tests inside and reporting back to the reporters:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"class Reporter {\n async onTestFileResult(test, testResult, aggregatedResult) {\n // ... reporter code ...\n }\n\n // ...\n};\n")),(0,r.yg)("p",{parentName:"li"},"Otherwise, Jest runs all the tests in the main process without spawning any other processes, but the reporting flow itself remains the same, as can be seen in the diagram:"),(0,r.yg)("p",{parentName:"li"},(0,r.yg)("img",{alt:"UML sequence diagram",src:n(47962).A,width:"591",height:"616"}))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"After all the tests have been executed, Jest calls ",(0,r.yg)("inlineCode",{parentName:"p"},"onRunComplete")," in the reporters, and this is the final phase for any reporter:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"class Reporter {\n async onRunComplete(testContexts, results) {\n // ... reporter code ...\n }\n\n // ...\n};\n"))),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("p",{parentName:"li"},"The last user-controlled hook is the global teardown. If a ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalteardown-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalTeardown"))," handler is defined, it is resolved and executed in the main process:"),(0,r.yg)("pre",{parentName:"li"},(0,r.yg)("code",{parentName:"pre",className:"language-js"},"module.exports = async function () {\n // ... global teardown code ...\n};\n")))),(0,r.yg)("h3",{id:"detox-lifecycle"},"Detox lifecycle"),(0,r.yg)("p",null,"Theoretically, Detox CLI could be totally agnostic about the test runner under the hood, but that would deprive us of some convenience.\nFor instance, there is a ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/testRunner#testrunnerretries-number"},"retry mechanism"),", built into Detox CLI, which can schedule extra runs for failed test files.\nThis requirement obliges Detox context to live longer than any test runner, and requires from the test runner to be able to report back to Detox CLI, whereas the resulting child process hierarchy can be broad and multi-tiered, e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"\u2514\u2500\u2500 detox test ...\n \u2514\u2500\u2500 jest ... --maxWorkers N\n \u251c\u2500\u2500 jest-worker (1)\n \u251c\u2500\u2500 ...\n \u2514\u2500\u2500 jest-worker (N)\n")),(0,r.yg)("p",null,"Even if we run Jest directly, without Detox CLI, there's still a one-to-many relationship between its processes:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-plain",metastring:"text",text:!0},"\u2514\u2500\u2500 jest ... --maxWorkers N\n \u251c\u2500\u2500 jest-worker (1)\n \u251c\u2500\u2500 ...\n \u2514\u2500\u2500 jest-worker (N)\n")),(0,r.yg)("p",null,"So, if we want to be on the safe side, every process should be able to communicate with the root process, where we have the primary context of Detox, and vice versa.\nRetrying failed tests is just one of numerous needs, and there are more:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"the primary context (and workers themselves, at times) needs to know how many workers are there;"),(0,r.yg)("li",{parentName:"ul"},"the workers should request from the primary context to allocate a device and return it back when they are done;"),(0,r.yg)("li",{parentName:"ul"},"any secondary context should be able to tell whether this is a first time it is running, or it is an ",(0,r.yg)("em",{parentName:"li"},"N"),"-th attempt already;")),(0,r.yg)("p",null,"This list can be continued and might expand even more with time, but the point is that Detox contexts will get instantiated as many times as child processes are created during the test session, and it should be something trivial to synchronize the primary and the secondary contexts to ensure a seamless experience."),(0,r.yg)("p",null,'It is worth mentioning that Jest\'s main process is ill-suited for taking a device and running the tests, as its purpose is to orchestrate the entire test session and not run the tests themselves.\nThis means that not all secondary contexts of Detox are "born equal" \u2013 most of them will be allocating a device for running tests, but at least one will be merely communicating with the primary context.'),(0,r.yg)("p",null,"This is exactly the reason why we call ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," in every child process, but sometimes it is not just a simple call, but an ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init({ workerId: null })"))," override to avoid creating a worker.\nHowever, the fact of initializing without a worker does not mean we can't call ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()"))," later. For example, if it turns out that Jest is running in a single process, then instead of creating two Detox contexts within the same process, we're going to reuse the existing one and just supplement it with the worker itself."),(0,r.yg)("p",null,"There's one more implicit thing that happens at the very beginning of ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init([options])"))," method, and that is the config resolution. It is also available as a separate method, ",(0,r.yg)("a",{parentName:"p",href:"#resolveconfigoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"resolveConfig([options])"))," based on the following considerations.\nWhen you use a test runner directly, without Detox CLI (e.g. ",(0,r.yg)("inlineCode",{parentName:"p"},"jest \u2026")," instead of ",(0,r.yg)("inlineCode",{parentName:"p"},"detox test \u2026"),"), then the test runner config gets resolved earlier than Detox config itself. That creates a dangerous scenario for dynamically generated configs:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="jest.config.js"',title:'"jest.config.js"'},"const { config } = require('detox/internals');\n\nmodule.exports = async () => {\n if (config.device.type === 'ios.simulator') {\n return { /* iOS-specific preset */ };\n } else {\n return { /* Android-specific preset */ };\n }\n};\n")),(0,r.yg)("p",null,"The problem in this case is that we are accessing an unresolved (yet) config. Of course, one could assume that it is possible to overcome with a plain ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," call like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="bad-idea.config.js"',title:'"bad-idea.config.js"'},"const { init, config } = require('detox/internals');\n\nmodule.exports = async () => {\n await init();\n\n if (config.device.type === 'ios.simulator') {\n // ... use config now ...\n }\n};\n")),(0,r.yg)("p",null,"This solution will work, but it is rather a bad one, since Jest config resolution is an asymmetrical step compared to ",(0,r.yg)("inlineCode",{parentName:"p"},"globalSetup")," and ",(0,r.yg)("inlineCode",{parentName:"p"},"globalTeardown"),". While solving one problem, it creates another one. Consider running this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-sh"},"jest --config bad-idea.config.js --showConfig\n")),(0,r.yg)("p",null,"For reference, when Jest runs with a ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/cli#--showconfig"},(0,r.yg)("inlineCode",{parentName:"a"},"--showConfig"))," option, all it does is ",(0,r.yg)("em",{parentName:"p"},"to resolve the config and to print it"),". Hence, neither ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalsetup-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalSetup"))," nor ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#globalteardown-string"},(0,r.yg)("inlineCode",{parentName:"a"},"globalTeardown"))," will be called, and the test runner will hang up since there's no one to call the ",(0,r.yg)("a",{parentName:"p",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup()"))," method which stops the ",(0,r.yg)("a",{parentName:"p",href:"https://www.npmjs.com/package/node-ipc"},"IPC server")," used for the communication between the primary and the secondary contexts."),(0,r.yg)("p",null,"Still, we have to be able to access the config early, and that is exactly why ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," method is a composite of ",(0,r.yg)("a",{parentName:"p",href:"#resolveconfigoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"resolveConfig()")),", the ",(0,r.yg)("em",{parentName:"p"},"actual")," init, and ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()")),". We could describe what it does on the high level with the following pseudocode:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"async function init(options = {}) {\n config = config || await resolveConfig();\n\n await logger.init(config);\n await ipcServer.init(config);\n // ... init more things ...\n\n if (options.workerId != null) {\n await installWorker();\n }\n}\n")),(0,r.yg)("p",null,"In other words, it will resolve config only if it has not been resolved before, and install a worker unless it has been forbidden explicitly.\nAnd even that we can ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()"))," later if we ever need it."),(0,r.yg)("p",null,"Now, when many details are clarified, we can review the actual sequence diagrams step by step. There are four scenarios depending on the initiator (Detox CLI or the test runner itself) and on child process hierarchy (a single process or parent-children)."),(0,r.yg)("p",null,"A few words about the diagram and its conventions:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"The ",(0,r.yg)("inlineCode",{parentName:"li"},"ClassName.0"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"ClassName.1"),", \u2026, ",(0,r.yg)("inlineCode",{parentName:"li"},"ClassName.N")," suffixes mean the index of the instance of the class created."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"DetoxCircusEnvironment.N")," is our custom ",(0,r.yg)("a",{parentName:"li",href:"https://jestjs.io/docs/configuration#testenvironment-string"},(0,r.yg)("inlineCode",{parentName:"a"},"testEnvironment"))," created one or multiple times in every Jest worker. Make sure to read about Jest ",(0,r.yg)("a",{parentName:"li",href:"https://jestjs.io/docs/configuration#testenvironment-string"},"test environments")," and look at the example section there for better understanding.")),(0,r.yg)(o.A,{mdxType:"Tabs"},(0,r.yg)(i.A,{value:"detox test \u2026 --maxWorkers N",mdxType:"TabItem"},(0,r.yg)(p,{mdxType:"LifecycleManyWorkersCLI"})),(0,r.yg)(i.A,{value:"jest \u2026 --maxWorkers N",mdxType:"TabItem"},(0,r.yg)(u,{mdxType:"LifecycleManyWorkersJest"})),(0,r.yg)(i.A,{value:"detox test \u2026 --runInBand",mdxType:"TabItem"},(0,r.yg)(h,{mdxType:"LifecycleSingleWorkerCLI"})),(0,r.yg)(i.A,{value:"jest \u2026 --runInBand",mdxType:"TabItem"},(0,r.yg)(N,{mdxType:"LifecycleSingleWorkerJest"}))),(0,r.yg)("h2",{id:"methods"},"Methods"),(0,r.yg)("admonition",{type:"info"},(0,r.yg)("p",{parentName:"admonition"},"Feel free to browse through ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/blob/master/detox/internals.d.ts"},"the typings file")," provided by Detox.")),(0,r.yg)("h3",{id:"resolveconfigoptions-promiseruntimeconfig"},(0,r.yg)("inlineCode",{parentName:"h3"},"resolveConfig([options])")," ","[","Promise","]"),(0,r.yg)("p",null,"Use sparingly for cases when you need to read Detox config before ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," is called."),(0,r.yg)("p",null,"If you use Detox with Jest, our default integration, the only place where you might need it is your ",(0,r.yg)("inlineCode",{parentName:"p"},"jest.config.js"),", e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="jest.config.js"',title:'"jest.config.js"'},"const { resolveConfig } = require('detox/internals');\n\nmodule.exports = async () => {\n /** @type {DetoxInternals.RuntimeConfig} */\n const config = await resolveConfig();\n\n return { /* Jest config */ };\n};\n")),(0,r.yg)("p",null,"For example, you could use that to evaluate the ",(0,r.yg)("a",{parentName:"p",href:"https://jestjs.io/docs/configuration#maxworkers-number--string"},(0,r.yg)("inlineCode",{parentName:"a"},"maxWorkers"))," count depending on the device type,\nbut please mind, though, that Detox allows to override test runner options via its own config, e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox.config.js"',title:'"detox.config.js"'},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n apps: { /* ... */ },\n devices: { /* ... */ },\n configurations: {\n 'ios.sim.release': {\n app: 'ios.release',\n device: 'iphone',\n testRunner: {\n args: {\n// highlight-next-line\n maxWorkers: process.env.CI === 'true' ? 3 : undefined,\n },\n },\n },\n 'android.emu.release': {\n app: 'android.release',\n device: 'nexus',\n testRunner: {\n args: {\n// highlight-next-line\n maxWorkers: process.env.CI === 'true' ? 2 : undefined,\n },\n },\n },\n },\n};\n")),(0,r.yg)("p",null,"This trick shown above allows to forward extra CLI arguments to ",(0,r.yg)("inlineCode",{parentName:"p"},"jest")," conditionally, e.g. when running in CI mode:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"CI=true detox test -c ios.sim.release\n# jest --config e2e/jest.config.js --maxWorkers 3\n")),(0,r.yg)("h3",{id:"getstatus-enum"},(0,r.yg)("inlineCode",{parentName:"h3"},"getStatus()")," ","[","enum]"),(0,r.yg)("p",null,"Returns one of statuses depending on what\u2019s going with the current Detox context:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"inactive")," \u2013 before ",(0,r.yg)("a",{parentName:"li",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," and after ",(0,r.yg)("a",{parentName:"li",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup()"))," is called."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"init")," \u2013 while ",(0,r.yg)("a",{parentName:"li",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," is executing."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"active")," \u2013 after ",(0,r.yg)("a",{parentName:"li",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init()"))," and before ",(0,r.yg)("a",{parentName:"li",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup()"))," is called."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"cleanup")," \u2013 while ",(0,r.yg)("a",{parentName:"li",href:"#cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"cleanup"))," is executing.")),(0,r.yg)("h3",{id:"initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"init([options])")," ","[","Promise]"),(0,r.yg)("p",null,"This is the phase where:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"a primary Detox context resolves its configuration, starts the logger, IPC server, and more;"),(0,r.yg)("li",{parentName:"ul"},"a secondary Detox context connects to IPC server and registers itself;"),(0,r.yg)("li",{parentName:"ul"},"if ",(0,r.yg)("inlineCode",{parentName:"li"},"workerId")," is not null, ",(0,r.yg)("a",{parentName:"li",href:"#installworkeroptions-promise"},"installs the worker"),";")),(0,r.yg)("p",null,"Accepts an optional parameter, ",(0,r.yg)("inlineCode",{parentName:"p"},"options")," object with the following properties, ",(0,r.yg)("em",{parentName:"p"},"all optional")," as well:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"cwd")," (string) \u2013 current working directory, used to ",(0,r.yg)("a",{parentName:"li",href:"/Detox/docs/config/overview#path-conventions"},"resolve Detox config"),"."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"argv")," (key-value map) \u2013 ",(0,r.yg)("strong",{parentName:"li"},"Internal!"),". CLI arguments parsed by Detox CLI."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testRunnerArgv")," (key-value map) \u2013 CLI arguments to be forwarded to the test runner."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"override")," (Partial",") \u2013 ad-hoc adjustments to deep merge with the file-based config."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"global")," \u2013 reference to a custom ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/globals.html"},"global")," scope, usually needed when your test runner ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/vm.html"},"uses sandboxing"),".\nThis prevents creating issues when a Detox context cannot be accessed from within the sandboxed environment."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"workerId")," \u2013 (string ","|"," null) a unique ID, e.g. ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-1"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-2"),". Giving ",(0,r.yg)("inlineCode",{parentName:"li"},"null")," disables installing the worker.")),(0,r.yg)("h3",{id:"installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"installWorker([options])")," ","[","Promise]"),(0,r.yg)("p",null,"This is the phase where Detox loads its expectation library and boots a device. You don't need to call it separately\nunless you use ",(0,r.yg)("a",{parentName:"p",href:"#initoptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"init({ workerId: null })"))," override."),(0,r.yg)("p",null,"Accepts an optional parameter, ",(0,r.yg)("inlineCode",{parentName:"p"},"options")," object with the following properties, ",(0,r.yg)("em",{parentName:"p"},"all optional")," as well:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"global")," \u2013 reference to a custom ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/globals.html"},"global")," scope, usually needed when your test runner ",(0,r.yg)("a",{parentName:"li",href:"https://nodejs.org/api/vm.html"},"uses sandboxing"),".\nThis prevents creating issues when a Detox context cannot be accessed from within the sandboxed environment."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"workerId")," \u2013 (string ","|"," null) a unique ID, e.g. ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-1"),", ",(0,r.yg)("inlineCode",{parentName:"li"},"worker-2"),". Giving ",(0,r.yg)("inlineCode",{parentName:"li"},"null")," disables installing the worker.")),(0,r.yg)("h3",{id:"uninstallworker-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"uninstallWorker()")," ","[","Promise]"),(0,r.yg)("p",null,"Deallocates the device. Most Client API (",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/device"},(0,r.yg)("inlineCode",{parentName:"a"},"device")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/matchers"},(0,r.yg)("inlineCode",{parentName:"a"},"by")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/actions"},(0,r.yg)("inlineCode",{parentName:"a"},"element")),", ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/expect"},(0,r.yg)("inlineCode",{parentName:"a"},"expect")),") will stop working, except for ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/logger"},"the logger"),"."),(0,r.yg)("h3",{id:"cleanup-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"cleanup()")," ","[","Promise]"),(0,r.yg)("p",null,"This method should be called when the main or child process is about to exit. Under the hood, it:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("a",{parentName:"li",href:"#uninstallworker-promise"},"uninstalls the worker")," if there is a worker installed;"),(0,r.yg)("li",{parentName:"ul"},"a secondary Detox context disconnects from the ",(0,r.yg)("a",{parentName:"li",href:"https://www.npmjs.com/package/node-ipc"},"IPC server"),";"),(0,r.yg)("li",{parentName:"ul"},"a primary Detox context stops the IPC server and collects the log artifacts.")),(0,r.yg)("h2",{id:"optional-lifecycle"},"Optional lifecycle"),(0,r.yg)("h3",{id:"reporttestresultsarray-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"reportTestResults(array)")," ","[","Promise]"),(0,r.yg)("admonition",{type:"note"},(0,r.yg)("p",{parentName:"admonition"},"This method has an effect only when the tests are run via ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test"},"Detox CLI"),".")),(0,r.yg)("p",null,"Reports to the primary context about failed tests that could have been re-run if ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/cli/test#options"},(0,r.yg)("inlineCode",{parentName:"a"},"-R, --retries"))," mechanism is enabled."),(0,r.yg)("p",null,"It takes one argument, an array of test file reports. Each report is an object with the following properties:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testFilePath")," (string) \u2014 global or relative path to the failed test file;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"success")," (boolean) \u2014 whether the test passed or not;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testExecError")," (optional error) \u2014 top-level error, use it only if the entire test file failed, e.g. due to a syntax error or environment setup failure;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"isPermanentFailure")," (optional boolean) \u2014 if the test failed, it should tell whether the failure is permanent. Permanent failure means that the test file should not be re-run. For instance, we use it to ",(0,r.yg)("a",{parentName:"li",href:"/Detox/docs/config/testRunner#testrunnerjestretryaftercircusretries-boolean"},"prevent double retries"),": with Detox CLI and with ",(0,r.yg)("a",{parentName:"li",href:"https://jestjs.io/docs/jest-object#jestretrytimesnumretries-options"},(0,r.yg)("inlineCode",{parentName:"a"},"jest.retryTimes()")),".")),(0,r.yg)("h3",{id:"onrundescribestartevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onRunDescribeStart(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports that the test runner started executing a test suite, e.g. a ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAll")," hook or a first test:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onRunDescribeStart({\n name: 'Suite name'\n});\n")),(0,r.yg)("h3",{id:"onteststartevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onTestStart(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports that the test runner started executing a specific test. Use ",(0,r.yg)("inlineCode",{parentName:"p"},"invocations")," when a test is being re-run after a failure:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestStart({\n title: 'should do something expected',\n fullName: 'Suite name should do something expected',\n invocations: 1,\n status: 'running',\n});\n")),(0,r.yg)("h3",{id:"onhookfailureevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onHookFailure(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports about an error in the midst of ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAll"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeEach"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"afterEach"),", ",(0,r.yg)("inlineCode",{parentName:"p"},"afterAll")," or any other hook. We use it, for example, to generate ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts"},"screenshot artifacts")," like ",(0,r.yg)("inlineCode",{parentName:"p"},"beforeAllFailure.png"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onHookFailure({\n error: new Error('Some assertion failed'),\n hook: 'beforeEach',\n});\n")),(0,r.yg)("h3",{id:"ontestfnfailureevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onTestFnFailure(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports about an error in the midst of a test function. We use it, for example, to generate ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts"},"screenshot artifacts")," like ",(0,r.yg)("inlineCode",{parentName:"p"},"testFnFailure.png"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestFnFailure({ error: new Error('Some assertion failed') });\n")),(0,r.yg)("h3",{id:"ontestdoneevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onTestDone(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports the final status of the test, ",(0,r.yg)("inlineCode",{parentName:"p"},"passed")," or ",(0,r.yg)("inlineCode",{parentName:"p"},"failed"),", e.g.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestDone({\n title: 'should do something expected',\n fullName: 'Suite name should do something expected',\n invocations: 1,\n status: 'failed',\n timedOut: false,\n});\n")),(0,r.yg)("p",null,"Besides collecting ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/artifacts"},"log, screenshot and video artifacts"),", this hook resets the pending network requests \u2013\nthese are usually some actions to which the app did not respond during the test. Setting ",(0,r.yg)("inlineCode",{parentName:"p"},"timedOut: true")," tells Detox to dump those pending requests, if there are any."),(0,r.yg)("p",null,"If your test runner supports re-running internally the failed tests, and, for example, your test passes on the second attempt, you would call the method with something like this:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onTestDone({\n title: 'should do something expected',\n fullName: 'Suite name should do something expected',\n invocations: 2,\n status: 'passed',\n});\n")),(0,r.yg)("h3",{id:"onrundescribefinishevent-promise"},(0,r.yg)("inlineCode",{parentName:"h3"},"onRunDescribeFinish(event)")," ","[","Promise]"),(0,r.yg)("p",null,(0,r.yg)("strong",{parentName:"p"},"Requires an installed worker."),"\nReports that the test runner has finished executing a test suite, e.g. all the ",(0,r.yg)("inlineCode",{parentName:"p"},"afterAll")," hooks have been executed or the last test has finished running:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"await onRunDescribeFinish({ name: 'Suite name' });\n")),(0,r.yg)("h2",{id:"properties"},"Properties"),(0,r.yg)("h3",{id:"config-runtimeconfig"},(0,r.yg)("inlineCode",{parentName:"h3"},"config")," ","[","RuntimeConfig]"),(0,r.yg)("p",null,"Open ",(0,r.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/blob/master/detox/internals.d.ts"},"the typings file")," and search for ",(0,r.yg)("inlineCode",{parentName:"p"},"RuntimeConfig"),"."),(0,r.yg)("p",null,"For the most part, this config is identical to what we describe in ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/overview"},"Config docs"),", except that it is non-optional.\nIn other words, even if you never customized your ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/config/session"},"Session config"),", you'll still be able to access some default\nvalues safely, without null checks."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"const { config } = require('detox/internals');\ntypeof config.session.autoStart // \"boolean\"\n")),(0,r.yg)("h3",{id:"session-sessionstate"},(0,r.yg)("inlineCode",{parentName:"h3"},"session")," ","[","SessionState]"),(0,r.yg)("p",null,"The session state contains the following read-only properties:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"id")," (string) \u2013 randomly generated ID for the entire Detox test session, including retries."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testResults")," (DetoxTestFileReport[]) \u2013 results of the prior test file executions, used by Detox CLI retry mechanism."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"testSessionIndex")," (number) \u2013 the retry index of the test session: ",(0,r.yg)("inlineCode",{parentName:"li"},"0.."),"."),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"workersCount")," (number) \u2013 count of Detox contexts with a worker installed.\nIf we oversimplify it, it reflects the count of allocated devices in the current test session.")),(0,r.yg)("p",null,"The session state, including the resolved config, is serialized by the primary context, so that the secondary Detox contexts can read it synchronously from a file at the earliest point possible.\nAfter the secondary contexts connect to the IPC server hosted by the primary context, they register themselves and get the up-to-date session state.\nThe IPC server broadcasts the updates to all the connected contexts on every action like ",(0,r.yg)("a",{parentName:"p",href:"#installworkeroptions-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"installWorker()"))," or ",(0,r.yg)("a",{parentName:"p",href:"#reporttestresultsarray-promise"},(0,r.yg)("inlineCode",{parentName:"a"},"reportTestResults()")),"."),(0,r.yg)("h3",{id:"log-logger"},(0,r.yg)("inlineCode",{parentName:"h3"},"log")," ","[","Logger]"),(0,r.yg)("p",null,"See ",(0,r.yg)("a",{parentName:"p",href:"/Detox/docs/api/logger"},"Logger API")," for all the details."),(0,r.yg)("p",null,"The only difference from the Client API here is that you don't have a predefined ",(0,r.yg)("inlineCode",{parentName:"p"},"user")," category, i.e.:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"const { log: logClient } = require('detox');\nconst { log: logInternal } = require('detox/internals');\n\n// oversimplified, it looks like:\nlogClient == logInternal.child({ cat: 'user' })\n")),(0,r.yg)("p",null,"For example, we leverage this for adding more ",(0,r.yg)("inlineCode",{parentName:"p"},"lifecycle")," events in our integration with Jest:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox/runners/jest/testEnvironment/index.js"',title:'"detox/runners/jest/testEnvironment/index.js"'},"class DetoxCircusEnvironment extends NodeEnvironment {\n constructor(config, context) {\n super(/* ... */);\n log.trace.begin({ cat: 'lifecycle' }, context.testPath);\n // ...\n }\n}\n")),(0,r.yg)("h3",{id:"tracing"},(0,r.yg)("inlineCode",{parentName:"h3"},"tracing")),(0,r.yg)("p",null,"An advanced API useful for creating reports based on logged Detox events."),(0,r.yg)("h4",{id:"tracingcreateeventstream"},(0,r.yg)("inlineCode",{parentName:"h4"},"tracing.createEventStream()")),(0,r.yg)("p",null,"Creates a readable stream of the currently recorded events in\n",(0,r.yg)("a",{parentName:"p",href:"https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU"},"Chrome Trace Event format"),"."),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-js"},"const { tracing } = require('detox/internals');\n\nasync function processDetoxEvents() {\n await new Promise((resolve, reject) => {\n tracing\n .createEventStream()\n .on('end', resolve)\n .on('error', reject)\n .on('data', (event) => {\n if (event.ph === 'B') { /* duration event (begin) */ }\n if (event.ph === 'E') { /* duration event (end) */ }\n if (event.ph === 'i') { /* instant event */ }\n });\n });\n}\n")),(0,r.yg)("p",null,"Please mind that you'll be getting a snapshot of events aggregated from all the sibling and child processes, and it never will be complete until the very end of the test session."),(0,r.yg)("p",null,"See also: ",(0,r.yg)("a",{parentName:"p",href:"https://wix-incubator.github.io/trace-event-lib/interfaces/DurationBeginEvent.html"},(0,r.yg)("inlineCode",{parentName:"a"},"DurationBeginEvent")),", ",(0,r.yg)("a",{parentName:"p",href:"https://wix-incubator.github.io/trace-event-lib/interfaces/DurationEndEvent.html"},(0,r.yg)("inlineCode",{parentName:"a"},"DurationEndEvent")),", ",(0,r.yg)("a",{parentName:"p",href:"https://wix-incubator.github.io/trace-event-lib/interfaces/InstantEvent.html"},(0,r.yg)("inlineCode",{parentName:"a"},"InstantEvent")),"."),(0,r.yg)("h3",{id:"worker-object"},(0,r.yg)("inlineCode",{parentName:"h3"},"worker")," ","[","object]"),(0,r.yg)("p",null,"Not documented on purpose. Provides direct access to the object which holds the device driver, websocket client, matchers, expectations, etc."))}D.isMDXComponent=!0},82373:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-cli-many-workers-95ab2193ce10864c0742f01ffd30b58b.svg"},50765:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-cli-single-worker-6672b7356e2146d40f39f80145780949.svg"},82415:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-jest-many-workers-ce60aaacc2101a9b810d2d22fe0df51b.svg"},85287:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/boot-jest-single-worker-278e1383ad4f31570f073fa12689aec6.svg"},47962:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/jest-diagram-runInBand-b0e7564080d4f6a0a03e4dbcf6b0da7f.svg"},71198:(e,t,n)=>{n.d(t,{A:()=>a});const a=n.p+"assets/images/jest-diagram-d957dd3e62301816b44197ef0a4abee0.svg"}}]); \ No newline at end of file diff --git a/assets/js/ae694851.e7b6bff9.js b/assets/js/ae694851.b685311d.js similarity index 54% rename from assets/js/ae694851.e7b6bff9.js rename to assets/js/ae694851.b685311d.js index a912205297..bc7d079044 100644 --- a/assets/js/ae694851.e7b6bff9.js +++ b/assets/js/ae694851.b685311d.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[5928],{15680:(e,t,a)=>{a.d(t,{xA:()=>c,yg:()=>g});var n=a(96540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),u=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},c=function(e){var t=u(e.components);return n.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(a),m=r,g=p["".concat(s,".").concat(m)]||p[m]||d[m]||l;return a?n.createElement(g,o(o({ref:t},c),{},{components:a})):n.createElement(g,o({ref:t},c))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=a.length,o=new Array(l);o[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:r,o[1]=i;for(var u=2;u{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>N});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),i=a(56347),s=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.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:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(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,i.W6)(),l=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,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function y(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,i]=(0,r.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:l}))),[s,u]=g({queryString:a,groupId:n}),[p,y]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&i(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var f=a(92303);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==i&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.A)("tabs__item",h.tabItem,o?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function x(e){const t=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",h.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function N(e){const t=(0,f.A)();return r.createElement(x,(0,n.A)({key:String(t)},e))}},33511:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=a(11470),o=a(19365);const i={},s="Uninstalling Detox",u={unversionedId:"guide/uninstalling",id:"version-20.x/guide/uninstalling",title:"Uninstalling Detox",description:"Installing and using Detox implies certain side effects: cloned devices, cache files, state files, temporary files, etc.",source:"@site/versioned_docs/version-20.x/guide/uninstalling.md",sourceDirName:"guide",slug:"/guide/uninstalling",permalink:"/Detox/docs/guide/uninstalling",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/guide/uninstalling.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Cucumber.js Integration",permalink:"/Detox/docs/guide/cucumber-js-integration"},next:{title:"Dealing With Problems With Building the App & Detox",permalink:"/Detox/docs/troubleshooting/building-the-app"}},c={},p=[{value:"Detox Framework Cache",id:"detox-framework-cache",level:2},{value:"Test Session State",id:"test-session-state",level:2},{value:"Cloned Simulators (macOS)",id:"cloned-simulators-macos",level:2},{value:"Remnants of Artifacts",id:"remnants-of-artifacts",level:2},{value:"iOS",id:"ios",level:3},{value:"Android",id:"android",level:3},{value:"Detox CLI",id:"detox-cli",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"uninstalling-detox"},"Uninstalling Detox"),(0,r.yg)("p",null,"Installing and using Detox implies certain side effects: cloned devices, cache files, state files, temporary files, etc."),(0,r.yg)("p",null,"This checklist might come in handy whenever you have to make a clean uninstallation after using Detox."),(0,r.yg)("h2",{id:"detox-framework-cache"},"Detox Framework Cache"),(0,r.yg)("p",null,"Every install of Detox also triggers a ",(0,r.yg)("inlineCode",{parentName:"p"},"postinstall")," script in its ",(0,r.yg)("inlineCode",{parentName:"p"},"package.json"),", which builds (or unpacks) ",(0,r.yg)("inlineCode",{parentName:"p"},"Detox.framework")," into ",(0,r.yg)("inlineCode",{parentName:"p"},"~/Library/Detox"),"."),(0,r.yg)("p",null,"You can either delete the folder manually:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"rm -rf ~/Library/Detox\n")),(0,r.yg)("p",null,"or run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"detox clean-framework-cache\n")),(0,r.yg)("h2",{id:"test-session-state"},"Test Session State"),(0,r.yg)("p",null,"On every test run, Detox rewrites a few temporary files in ",(0,r.yg)("inlineCode",{parentName:"p"},"DETOX_LIBRARY_ROOT_PATH"),", i.e.:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("inlineCode",{parentName:"li"},"device.registry.json"),", to tell apart the busy and the available devices for use with multiple workers."),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("inlineCode",{parentName:"li"},"global-context.json.lock"),", to detect multiple concurrent instances of Detox.")),(0,r.yg)("p",null,"The location of ",(0,r.yg)("inlineCode",{parentName:"p"},"DETOX_LIBRARY_ROOT_PATH")," may vary depending on the operating system:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"macOS:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"~/Library/Detox"),"."))),(0,r.yg)("li",{parentName:"ul"},"Linux:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"$XDG_DATA_HOME/Detox"),", if ",(0,r.yg)("inlineCode",{parentName:"li"},"$XDG_DATA_HOME")," is defined;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"~/.local/share/Detox"),", otherwise."))),(0,r.yg)("li",{parentName:"ul"},"Windows:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"%LOCALAPPDATA%\\data\\Detox"),", if ",(0,r.yg)("inlineCode",{parentName:"li"},"%LOCALAPPDATA%")," is defined;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"%USERPROFILE%\\Application Data\\Detox"),", otherwise.")))),(0,r.yg)("h2",{id:"cloned-simulators-macos"},"Cloned Simulators (macOS)"),(0,r.yg)("p",null,'To support the "multiple workers" feature on iOS, Detox clones simulator instances when there aren\u2019t enough available ones.\nThe autogenerated simulators have names with ',(0,r.yg)("inlineCode",{parentName:"p"},"-Detox")," suffix appended, so you can easily spot them with:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},'xcrun simctl list | grep "-Detox"\n')),(0,r.yg)("p",null,"To delete a simulator, you can use:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"xcrun simctl delete \n")),(0,r.yg)("h2",{id:"remnants-of-artifacts"},"Remnants of Artifacts"),(0,r.yg)("p",null,"Forced exits may result in leaving some temporary files behind."),(0,r.yg)("h3",{id:"ios"},"iOS"),(0,r.yg)("p",null,"To ensure there are no temporary artifact files (logs, screenshots, etc.), you can run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"rm -rf $TMPDIR/*.detox.*\n")),(0,r.yg)("p",null,"If you wish to clean up your iOS simulators from the installed apps and other customizations, just run the erase procedure for the relevant ones:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"xcrun simctl erase \n")),(0,r.yg)("h3",{id:"android"},"Android"),(0,r.yg)("p",null,"The advice for iOS applies to the Android virtual devices as well. To wipe user files on a specific AVD, run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"emulator -avd -wipe-data\n")),(0,r.yg)("p",null,"If you have to clean temporary Detox files from an individual booted device, look out for files like ",(0,r.yg)("inlineCode",{parentName:"p"},"11159175_0.log")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"/sdcard")," folder.\nYou can try deleting them using a simple wildcard like below or use your own:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"adb -s shell rm -rf /sdcard/*_*.*\n")),(0,r.yg)("h2",{id:"detox-cli"},"Detox CLI"),(0,r.yg)("p",null,"If you have installed the official CLI wrapper for Detox, then make sure to uninstall it as well:"),(0,r.yg)(l.A,{groupId:"npm2yarn",mdxType:"Tabs"},(0,r.yg)(o.A,{value:"npm",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"npm uninstall detox-cli --global\n"))),(0,r.yg)(o.A,{value:"yarn",label:"Yarn",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"yarn global remove detox-cli\n"))),(0,r.yg)(o.A,{value:"pnpm",label:"pnpm",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"pnpm remove detox-cli --global\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[5928],{15680:(e,t,a)=>{a.d(t,{xA:()=>c,yg:()=>g});var n=a(96540);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),u=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},c=function(e){var t=u(e.components);return n.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(a),m=r,g=p["".concat(s,".").concat(m)]||p[m]||d[m]||l;return a?n.createElement(g,o(o({ref:t},c),{},{components:a})):n.createElement(g,o({ref:t},c))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=a.length,o=new Array(l);o[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:r,o[1]=i;for(var u=2;u{a.d(t,{A:()=>o});var n=a(96540),r=a(20053);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:a,className:o}=e;return n.createElement("div",{role:"tabpanel",className:(0,r.A)(l.tabItem,o),hidden:a},t)}},11470:(e,t,a)=>{a.d(t,{A:()=>w});var n=a(58168),r=a(96540),l=a(20053),o=a(23104),i=a(56347),s=a(57485),u=a(31682),c=a(89466);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.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:r}}=e;return{value:t,label:a,attributes:n,default:r}}))}function d(e){const{values:t,children:a}=e;return(0,r.useMemo)((()=>{const e=t??p(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,i.W6)(),l=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,s.aZ)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(n.location.search);t.set(l,e),n.replace({...n.location,search:t.toString()})}),[l,n])]}function y(e){const{defaultValue:t,queryString:a=!1,groupId:n}=e,l=d(e),[o,i]=(0,r.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:l}))),[s,u]=g({queryString:a,groupId:n}),[p,y]=function(e){let{groupId:t}=e;const a=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,l]=(0,c.Dv)(a);return[n,(0,r.useCallback)((e=>{a&&l.set(e)}),[a,l])]}({groupId:n}),f=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{f&&i(f)}),[f]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var f=a(92303);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function b(e){let{className:t,block:a,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.a_)(),d=e=>{const t=e.currentTarget,a=c.indexOf(t),n=u[a].value;n!==i&&(p(t),s(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;t=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;t=c[a]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.A)("tabs",{"tabs--block":a},t)},u.map((e=>{let{value:t,label:a,attributes:o}=e;return r.createElement("li",(0,n.A)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.A)("tabs__item",h.tabItem,o?.className,{"tabs__item--active":i===t})}),a??t)})))}function v(e){let{lazy:t,children:a,selectedValue:n}=e;const l=(Array.isArray(a)?a:[a]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===n));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function x(e){const t=y(e);return r.createElement("div",{className:(0,l.A)("tabs-container",h.tabList)},r.createElement(b,(0,n.A)({},e,t)),r.createElement(v,(0,n.A)({},e,t)))}function w(e){const t=(0,f.A)();return r.createElement(x,(0,n.A)({key:String(t)},e))}},33511:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>g,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var n=a(58168),r=(a(96540),a(15680)),l=a(11470),o=a(19365);const i={},s="Uninstalling Detox",u={unversionedId:"guide/uninstalling",id:"version-20.x/guide/uninstalling",title:"Uninstalling Detox",description:"Installing and using Detox implies certain side effects: cloned devices, cache files, state files, temporary files, etc.",source:"@site/versioned_docs/version-20.x/guide/uninstalling.md",sourceDirName:"guide",slug:"/guide/uninstalling",permalink:"/Detox/docs/guide/uninstalling",draft:!1,editUrl:"https://github.com/wix/Detox/edit/master/docs/versioned_docs/version-20.x/guide/uninstalling.md",tags:[],version:"20.x",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Cucumber.js Integration",permalink:"/Detox/docs/guide/cucumber-js-integration"},next:{title:"Natural Language Testing with Detox Copilot",permalink:"/Detox/docs/copilot/testing-with-copilot"}},c={},p=[{value:"Detox Framework Cache",id:"detox-framework-cache",level:2},{value:"Test Session State",id:"test-session-state",level:2},{value:"Cloned Simulators (macOS)",id:"cloned-simulators-macos",level:2},{value:"Remnants of Artifacts",id:"remnants-of-artifacts",level:2},{value:"iOS",id:"ios",level:3},{value:"Android",id:"android",level:3},{value:"Detox CLI",id:"detox-cli",level:2}],d={toc:p},m="wrapper";function g(e){let{components:t,...a}=e;return(0,r.yg)(m,(0,n.A)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.yg)("h1",{id:"uninstalling-detox"},"Uninstalling Detox"),(0,r.yg)("p",null,"Installing and using Detox implies certain side effects: cloned devices, cache files, state files, temporary files, etc."),(0,r.yg)("p",null,"This checklist might come in handy whenever you have to make a clean uninstallation after using Detox."),(0,r.yg)("h2",{id:"detox-framework-cache"},"Detox Framework Cache"),(0,r.yg)("p",null,"Every install of Detox also triggers a ",(0,r.yg)("inlineCode",{parentName:"p"},"postinstall")," script in its ",(0,r.yg)("inlineCode",{parentName:"p"},"package.json"),", which builds (or unpacks) ",(0,r.yg)("inlineCode",{parentName:"p"},"Detox.framework")," into ",(0,r.yg)("inlineCode",{parentName:"p"},"~/Library/Detox"),"."),(0,r.yg)("p",null,"You can either delete the folder manually:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"rm -rf ~/Library/Detox\n")),(0,r.yg)("p",null,"or run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"detox clean-framework-cache\n")),(0,r.yg)("h2",{id:"test-session-state"},"Test Session State"),(0,r.yg)("p",null,"On every test run, Detox rewrites a few temporary files in ",(0,r.yg)("inlineCode",{parentName:"p"},"DETOX_LIBRARY_ROOT_PATH"),", i.e.:"),(0,r.yg)("ol",null,(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("inlineCode",{parentName:"li"},"device.registry.json"),", to tell apart the busy and the available devices for use with multiple workers."),(0,r.yg)("li",{parentName:"ol"},(0,r.yg)("inlineCode",{parentName:"li"},"global-context.json.lock"),", to detect multiple concurrent instances of Detox.")),(0,r.yg)("p",null,"The location of ",(0,r.yg)("inlineCode",{parentName:"p"},"DETOX_LIBRARY_ROOT_PATH")," may vary depending on the operating system:"),(0,r.yg)("ul",null,(0,r.yg)("li",{parentName:"ul"},"macOS:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"~/Library/Detox"),"."))),(0,r.yg)("li",{parentName:"ul"},"Linux:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"$XDG_DATA_HOME/Detox"),", if ",(0,r.yg)("inlineCode",{parentName:"li"},"$XDG_DATA_HOME")," is defined;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"~/.local/share/Detox"),", otherwise."))),(0,r.yg)("li",{parentName:"ul"},"Windows:",(0,r.yg)("ul",{parentName:"li"},(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"%LOCALAPPDATA%\\data\\Detox"),", if ",(0,r.yg)("inlineCode",{parentName:"li"},"%LOCALAPPDATA%")," is defined;"),(0,r.yg)("li",{parentName:"ul"},(0,r.yg)("inlineCode",{parentName:"li"},"%USERPROFILE%\\Application Data\\Detox"),", otherwise.")))),(0,r.yg)("h2",{id:"cloned-simulators-macos"},"Cloned Simulators (macOS)"),(0,r.yg)("p",null,'To support the "multiple workers" feature on iOS, Detox clones simulator instances when there aren\u2019t enough available ones.\nThe autogenerated simulators have names with ',(0,r.yg)("inlineCode",{parentName:"p"},"-Detox")," suffix appended, so you can easily spot them with:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},'xcrun simctl list | grep "-Detox"\n')),(0,r.yg)("p",null,"To delete a simulator, you can use:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"xcrun simctl delete \n")),(0,r.yg)("h2",{id:"remnants-of-artifacts"},"Remnants of Artifacts"),(0,r.yg)("p",null,"Forced exits may result in leaving some temporary files behind."),(0,r.yg)("h3",{id:"ios"},"iOS"),(0,r.yg)("p",null,"To ensure there are no temporary artifact files (logs, screenshots, etc.), you can run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"rm -rf $TMPDIR/*.detox.*\n")),(0,r.yg)("p",null,"If you wish to clean up your iOS simulators from the installed apps and other customizations, just run the erase procedure for the relevant ones:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"xcrun simctl erase \n")),(0,r.yg)("h3",{id:"android"},"Android"),(0,r.yg)("p",null,"The advice for iOS applies to the Android virtual devices as well. To wipe user files on a specific AVD, run:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"emulator -avd -wipe-data\n")),(0,r.yg)("p",null,"If you have to clean temporary Detox files from an individual booted device, look out for files like ",(0,r.yg)("inlineCode",{parentName:"p"},"11159175_0.log")," in ",(0,r.yg)("inlineCode",{parentName:"p"},"/sdcard")," folder.\nYou can try deleting them using a simple wildcard like below or use your own:"),(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"adb -s shell rm -rf /sdcard/*_*.*\n")),(0,r.yg)("h2",{id:"detox-cli"},"Detox CLI"),(0,r.yg)("p",null,"If you have installed the official CLI wrapper for Detox, then make sure to uninstall it as well:"),(0,r.yg)(l.A,{groupId:"npm2yarn",mdxType:"Tabs"},(0,r.yg)(o.A,{value:"npm",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"npm uninstall detox-cli --global\n"))),(0,r.yg)(o.A,{value:"yarn",label:"Yarn",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"yarn global remove detox-cli\n"))),(0,r.yg)(o.A,{value:"pnpm",label:"pnpm",mdxType:"TabItem"},(0,r.yg)("pre",null,(0,r.yg)("code",{parentName:"pre",className:"language-bash"},"pnpm remove detox-cli --global\n")))))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c7f75652.bd985fd6.js b/assets/js/c7f75652.bd985fd6.js deleted file mode 100644 index 93f240c3a0..0000000000 --- a/assets/js/c7f75652.bd985fd6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[8716],{15680:(e,t,o)=>{o.d(t,{xA:()=>p,yg:()=>y});var n=o(96540);function a(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function i(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function r(e){for(var t=1;t=0||(a[o]=e[o]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(a[o]=e[o])}return a}var s=n.createContext({}),g=function(e){var t=n.useContext(s),o=t;return e&&(o="function"==typeof e?e(t):r(r({},t),e)),o},p=function(e){var t=g(e.components);return n.createElement(s.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var o=e.components,a=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=g(o),d=a,y=u["".concat(s,".").concat(d)]||u[d]||c[d]||i;return o?n.createElement(y,r(r({ref:t},p),{},{components:o})):n.createElement(y,r({ref:t},p))}));function y(e,t){var o=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=o.length,r=new Array(i);r[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:a,r[1]=l;for(var g=2;g{o.r(t),o.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>g});var n=o(58168),a=(o(96540),o(15680));const i={authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},r="Introducing Detox Copilot",l={permalink:"/Detox/blog/2024/09/30/detox-copilot-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2024-09-30-detox-copilot-is-out.md",source:"@site/blog/2024-09-30-detox-copilot-is-out.md",title:"Introducing Detox Copilot",description:"Detox Copilot: Write Tests in Natural Language",date:"2024-09-30T00:00:00.000Z",formattedDate:"September 30, 2024",tags:[{label:"minor-release",permalink:"/Detox/blog/tags/minor-release"},{label:"detox-copilot",permalink:"/Detox/blog/tags/detox-copilot"},{label:"ai-integration",permalink:"/Detox/blog/tags/ai-integration"}],readingTime:3.27,hasTruncateMarker:!1,authors:[{name:"Asaf Korem",title:"Detox Core Contributor",url:"https://github.com/asafkorem",imageURL:"https://github.com/asafkorem.png",key:"asafkorem"}],frontMatter:{authors:["asafkorem"],tags:["minor-release","detox-copilot","ai-integration"]},nextItem:{title:"Detox 20 is out",permalink:"/Detox/blog/2022/11/10/detox-20-is-out"}},s={authorsImageUrls:[void 0]},g=[{value:"Detox Copilot: Write Tests in Natural Language",id:"detox-copilot-write-tests-in-natural-language",level:2},{value:"Why Natural Language Testing?",id:"why-natural-language-testing",level:3},{value:"Key Features of Detox Copilot",id:"key-features-of-detox-copilot",level:2},{value:"Write Tests in Plain Text",id:"write-tests-in-plain-text",level:3},{value:"Seamless Integration with Detox",id:"seamless-integration-with-detox",level:3},{value:"LLM-Agnostic Design",id:"llm-agnostic-design",level:3},{value:"How Detox Copilot Works",id:"how-detox-copilot-works",level:2},{value:"Getting Started with Detox Copilot",id:"getting-started-with-detox-copilot",level:2},{value:"Extending Beyond Detox",id:"extending-beyond-detox",level:2},{value:"Learn More",id:"learn-more",level:2},{value:"Join the Future of Testing",id:"join-the-future-of-testing",level:2}],p={toc:g},u="wrapper";function c(e){let{components:t,...i}=e;return(0,a.yg)(u,(0,n.A)({},p,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("h2",{id:"detox-copilot-write-tests-in-natural-language"},"Detox Copilot: Write Tests in Natural Language"),(0,a.yg)("p",null,"We're excited to announce ",(0,a.yg)("strong",{parentName:"p"},"Detox Copilot"),", a groundbreaking feature that brings natural language testing to Detox. With Detox Copilot, you can now write end-to-end tests using plain textual commands, making test creation more intuitive and accessible than ever."),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Detox Copilot in action GIF",src:o(16031).A,width:"600",height:"388"})),(0,a.yg)("p",null,"Detox Copilot leverages advanced Large Language Models (LLMs) to interpret natural language instructions and translate them into Detox actions and assertions. This means you can describe your test scenarios in everyday language, aligning perfectly with ",(0,a.yg)("strong",{parentName:"p"},"Behavior-Driven Development (BDD)")," principles."),(0,a.yg)("h3",{id:"why-natural-language-testing"},"Why Natural Language Testing?"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Improved Collaboration"),": Teams can collaborate more effectively, as tests are written in plain language understandable by developers, QA engineers, and non-technical stakeholders alike."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Faster Test Creation"),": Reduce the time spent writing and maintaining complex test scripts."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Enhanced Test Coverage"),": Lower the barrier to writing tests, encouraging more comprehensive testing."),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("strong",{parentName:"li"},"Reduced Maintenance Costs"),": Thanks to the decoupling from specific matchers (e.g., avoiding brittle XPath selectors or relying on ",(0,a.yg)("inlineCode",{parentName:"li"},"testID")," attributes commonly used in React Native apps), tests are less prone to breaking when the UI changes, leading to lower maintenance overhead.")),(0,a.yg)("h2",{id:"key-features-of-detox-copilot"},"Key Features of Detox Copilot"),(0,a.yg)("h3",{id:"write-tests-in-plain-text"},"Write Tests in Plain Text"),(0,a.yg)("p",null,"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-javascript"},"it('should navigate and add a product to the cart', async () => {\n await copilot.perform(\n 'Navigate to the \"Products\" page',\n 'Tap on the \"Add to Cart\" button for the first product',\n 'Verify that the \"Added to Cart\" pop-up is displayed'\n );\n});\n")),(0,a.yg)("h3",{id:"seamless-integration-with-detox"},"Seamless Integration with Detox"),(0,a.yg)("p",null,"Detox Copilot is built into Detox and requires no additional installation. Simply initialize it in your test setup, and you're ready to start writing natural language tests."),(0,a.yg)("h3",{id:"llm-agnostic-design"},"LLM-Agnostic Design"),(0,a.yg)("p",null,"Detox Copilot uses LLMs to interpret instructions but is designed to be LLM-agnostic. This means you can connect it to your preferred language model service, offering flexibility and future-proofing your testing strategy."),(0,a.yg)("h2",{id:"how-detox-copilot-works"},"How Detox Copilot Works"),(0,a.yg)("p",null,"Once you've written your tests using natural language instructions, Detox Copilot takes care of the rest.\nHere is a high-level overview of the execution flow:"),(0,a.yg)("ol",null,(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Gather Context"),": Collect relevant app state, view hierarchy, and previous step results."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Interpret Intent"),": Use the LLM to interpret the natural language instruction."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Generate Code"),": Create the appropriate Detox commands."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Execute Action"),": Run the generated Detox code."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Cache Results"),": Store execution results to optimize future runs."),(0,a.yg)("li",{parentName:"ol"},(0,a.yg)("strong",{parentName:"li"},"Provide Feedback"),": Return values or confirm actions for subsequent steps.")),(0,a.yg)("p",null,"By combining these steps, Detox Copilot effectively bridges the gap between natural language instructions and concrete test actions."),(0,a.yg)("admonition",{type:"info"},(0,a.yg)("p",{parentName:"admonition"},"Check Detox Copilot ",(0,a.yg)("strong",{parentName:"p"},(0,a.yg)("a",{parentName:"strong",href:"/docs/next/copilot/technical-overview"},"Technical Overview"))," for a detailed explanation of the building blocks and the execution flow.")),(0,a.yg)("h2",{id:"getting-started-with-detox-copilot"},"Getting Started with Detox Copilot"),(0,a.yg)("p",null,"Getting started with Detox Copilot is easy. Simply initialize Copilot in your test setup and start writing tests using natural language instructions."),(0,a.yg)("p",null,"Check our ",(0,a.yg)("a",{parentName:"p",href:"/docs/next/copilot/testing-with-copilot"},"Testing with Copilot guide")," for detailed instructions on setting up and writing tests with Detox Copilot."),(0,a.yg)("h2",{id:"extending-beyond-detox"},"Extending Beyond Detox"),(0,a.yg)("p",null,"Detox Copilot is built on a standalone core library called ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix-incubator/detox-copilot"},"detox-copilot")," designed to interpret natural language testing instructions and generate test code. Though initially developed for Detox, it can be extended to work with other testing frameworks."),(0,a.yg)("h2",{id:"learn-more"},"Learn More"),(0,a.yg)("p",null,"For detailed guidance, check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/next/copilot/testing-with-copilot"},"Testing with Copilot guide")," and the ",(0,a.yg)("a",{parentName:"p",href:"/docs/next/api/copilot"},"Detox Copilot API Documentation"),"."),(0,a.yg)("h2",{id:"join-the-future-of-testing"},"Join the Future of Testing"),(0,a.yg)("p",null,"Detox Copilot represents a major step forward in making end-to-end testing more accessible and efficient. By embracing natural language testing, you can enhance collaboration, speed up test creation, and improve overall test coverage."),(0,a.yg)("p",null,"We're ",(0,a.yg)("strong",{parentName:"p"},"excited")," to see how you'll leverage Detox Copilot in your tests! Share your experiences, feedback, and suggestions with us as we continue to refine and expand this groundbreaking feature."))}c.isMDXComponent=!0},16031:(e,t,o)=>{o.d(t,{A:()=>n});const n=o.p+"assets/images/copilot-demo-5c8641f24001c1d6e3effd175dc8ec2b.gif"}}]); \ No newline at end of file diff --git a/assets/js/d350cff3.31339126.js b/assets/js/d350cff3.31339126.js new file mode 100644 index 0000000000..fb2a3764b9 --- /dev/null +++ b/assets/js/d350cff3.31339126.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2525],{84563:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"20.x","label":"20.x","banner":null,"badge":true,"noIndex":false,"className":"docs-version-20.x","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"category","label":"Introduction","items":[{"type":"link","label":"Getting Started","href":"/Detox/docs/introduction/getting-started","docId":"introduction/getting-started"},{"type":"link","label":"Environment Setup","href":"/Detox/docs/introduction/environment-setup","docId":"introduction/environment-setup"},{"type":"link","label":"Project Setup","href":"/Detox/docs/introduction/project-setup","docId":"introduction/project-setup"},{"type":"link","label":"Your First Test","href":"/Detox/docs/introduction/your-first-test","docId":"introduction/your-first-test"},{"type":"link","label":"How to Debug","href":"/Detox/docs/introduction/debugging","docId":"introduction/debugging"},{"type":"link","label":"Preparing for CI","href":"/Detox/docs/introduction/preparing-for-ci","docId":"introduction/preparing-for-ci"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Guides","items":[{"type":"link","label":"Investigating Failures","href":"/Detox/docs/guide/investigating-test-failure","docId":"guide/investigating-test-failure"},{"type":"link","label":"Adding test ID\'s to your components","href":"/Detox/docs/guide/test-id","docId":"guide/test-id"},{"type":"link","label":"Parallel Test Execution","href":"/Detox/docs/guide/parallel-test-execution","docId":"guide/parallel-test-execution"},{"type":"link","label":"Using TypeScript","href":"/Detox/docs/guide/typescript","docId":"guide/typescript"},{"type":"link","label":"Testing WebViews","href":"/Detox/docs/guide/testing-webviews","docId":"guide/testing-webviews"},{"type":"link","label":"Using Genymotion SaaS","href":"/Detox/docs/guide/genymotion-saas","docId":"guide/genymotion-saas"},{"type":"link","label":"Taking Screenshots","href":"/Detox/docs/guide/taking-screenshots","docId":"guide/taking-screenshots"},{"type":"link","label":"Mocking","href":"/Detox/docs/guide/mocking","docId":"guide/mocking"},{"type":"link","label":"Using Launch Arguments","href":"/Detox/docs/guide/launch-args","docId":"guide/launch-args"},{"type":"link","label":"Mocking Open With URL (Deep Links)","href":"/Detox/docs/guide/mocking-open-with-url","docId":"guide/mocking-open-with-url"},{"type":"link","label":"Mocking User Notifications","href":"/Detox/docs/guide/mocking-user-notifications","docId":"guide/mocking-user-notifications"},{"type":"link","label":"Mocking User Activity","href":"/Detox/docs/guide/mocking-user-activity","docId":"guide/mocking-user-activity"},{"type":"link","label":"Developing Your App While Writing Tests","href":"/Detox/docs/guide/developing-while-writing-tests","docId":"guide/developing-while-writing-tests"},{"type":"link","label":"Setting Up an Android Development & Testing Environment","href":"/Detox/docs/guide/android-dev-env","docId":"guide/android-dev-env"},{"type":"link","label":"ProGuard configuration","href":"/Detox/docs/guide/proguard-configuration","docId":"guide/proguard-configuration"},{"type":"link","label":"Cucumber.js Integration","href":"/Detox/docs/guide/cucumber-js-integration","docId":"guide/cucumber-js-integration"},{"type":"link","label":"Uninstalling Detox","href":"/Detox/docs/guide/uninstalling","docId":"guide/uninstalling"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Detox Copilot","items":[{"type":"link","label":"Natural Language Testing with Detox Copilot","href":"/Detox/docs/copilot/testing-with-copilot","docId":"copilot/testing-with-copilot"},{"type":"link","label":"Detox Copilot Best Practices","href":"/Detox/docs/copilot/best-practices","docId":"copilot/best-practices"},{"type":"link","label":"Technical Overview","href":"/Detox/docs/copilot/technical-overview","docId":"copilot/technical-overview"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Troubleshooting","items":[{"type":"link","label":"Dealing With Problems With Building the App & Detox","href":"/Detox/docs/troubleshooting/building-the-app","docId":"troubleshooting/building-the-app"},{"type":"link","label":"Dealing With Problems With Running Tests","href":"/Detox/docs/troubleshooting/running-tests","docId":"troubleshooting/running-tests"},{"type":"link","label":"Dealing With Synchronization Issues in Tests","href":"/Detox/docs/troubleshooting/synchronization","docId":"troubleshooting/synchronization"},{"type":"link","label":"Dealing With Flakiness in Tests","href":"/Detox/docs/troubleshooting/flakiness","docId":"troubleshooting/flakiness"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Migration Guide","href":"/Detox/docs/guide/migration","docId":"guide/migration"}],"apiSidebar":[{"type":"category","label":"Config file","items":[{"type":"link","label":"Overview","href":"/Detox/docs/config/overview","docId":"config/overview"},{"type":"link","label":"Devices","href":"/Detox/docs/config/devices","docId":"config/devices"},{"type":"link","label":"Apps","href":"/Detox/docs/config/apps","docId":"config/apps"},{"type":"link","label":"Artifacts","href":"/Detox/docs/config/artifacts","docId":"config/artifacts"},{"type":"link","label":"Behavior","href":"/Detox/docs/config/behavior","docId":"config/behavior"},{"type":"link","label":"Logger","href":"/Detox/docs/config/logger","docId":"config/logger"},{"type":"link","label":"Session","href":"/Detox/docs/config/session","docId":"config/session"},{"type":"link","label":"Test runner","href":"/Detox/docs/config/testRunner","docId":"config/testRunner"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Command Line Interface","items":[{"type":"link","label":"detox","href":"/Detox/docs/cli/overview","docId":"cli/overview"},{"type":"link","label":"detox init","href":"/Detox/docs/cli/init","docId":"cli/init"},{"type":"link","label":"detox build","href":"/Detox/docs/cli/build","docId":"cli/build"},{"type":"link","label":"detox start","href":"/Detox/docs/cli/start","docId":"cli/start"},{"type":"link","label":"detox test","href":"/Detox/docs/cli/test","docId":"cli/test"},{"type":"link","label":"detox recorder","href":"/Detox/docs/cli/recorder","docId":"cli/recorder"},{"type":"link","label":"detox build-framework-cache","href":"/Detox/docs/cli/build-framework-cache","docId":"cli/build-framework-cache"},{"type":"link","label":"detox clean-framework-cache","href":"/Detox/docs/cli/clean-framework-cache","docId":"cli/clean-framework-cache"},{"type":"link","label":"detox rebuild-framework-cache","href":"/Detox/docs/cli/rebuild-framework-cache","docId":"cli/rebuild-framework-cache"},{"type":"link","label":"detox reset-lock-file","href":"/Detox/docs/cli/reset-lock-file","docId":"cli/reset-lock-file"},{"type":"link","label":"detox run-server","href":"/Detox/docs/cli/run-server","docId":"cli/run-server"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Client API","items":[{"type":"link","label":"Device","href":"/Detox/docs/api/device","docId":"api/device"},{"type":"link","label":"Matchers","href":"/Detox/docs/api/matchers","docId":"api/matchers"},{"type":"link","label":"Actions","href":"/Detox/docs/api/actions","docId":"api/actions"},{"type":"link","label":"Expect","href":"/Detox/docs/api/expect","docId":"api/expect"},{"type":"link","label":"Web Views","href":"/Detox/docs/api/webviews","docId":"api/webviews"},{"type":"link","label":"System","href":"/Detox/docs/api/system","docId":"api/system"},{"type":"link","label":"Logger","href":"/Detox/docs/api/logger","docId":"api/logger"},{"type":"link","label":"Detox Copilot","href":"/Detox/docs/api/copilot","docId":"api/copilot"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Internals API","href":"/Detox/docs/api/internals","docId":"api/internals"},{"type":"category","label":"Tech Articles","items":[{"type":"link","label":"Design Principles","href":"/Detox/docs/articles/design-principles","docId":"articles/design-principles"},{"type":"link","label":"How Detox Works","href":"/Detox/docs/articles/how-detox-works","docId":"articles/how-detox-works"},{"type":"link","label":"Third-Party Drivers","href":"/Detox/docs/articles/third-party-drivers","docId":"articles/third-party-drivers"}],"collapsed":true,"collapsible":true}],"contributeSidebar":[{"type":"link","label":"Contributing to Detox","href":"/Detox/docs/contributing","docId":"contributing"},{"type":"category","label":"Questions & Answers","items":[{"type":"link","label":"Asking Questions","href":"/Detox/docs/contributing/questions/asking-questions","docId":"contributing/questions/asking-questions"},{"type":"link","label":"Answering Questions","href":"/Detox/docs/contributing/questions/answering-questions","docId":"contributing/questions/answering-questions"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Reporting Bugs","href":"/Detox/docs/contributing/reporting-bugs","docId":"contributing/reporting-bugs"},{"type":"link","label":"Feedback and Suggestions","href":"/Detox/docs/contributing/feature-requests","docId":"contributing/feature-requests"},{"type":"category","label":"Code Changes","items":[{"type":"link","label":"Overview","href":"/Detox/docs/contributing/code/overview","docId":"contributing/code/overview"},{"type":"link","label":"Setting up the Development Environment","href":"/Detox/docs/contributing/code/setting-up-the-dev-environment","docId":"contributing/code/setting-up-the-dev-environment"},{"type":"link","label":"Building and Testing","href":"/Detox/docs/contributing/code/building-and-testing","docId":"contributing/code/building-and-testing"},{"type":"link","label":"Example Projects","href":"/Detox/docs/contributing/code/example-projects","docId":"contributing/code/example-projects"},{"type":"link","label":"Submitting Pull Requests","href":"/Detox/docs/contributing/code/submitting-pull-requests","docId":"contributing/code/submitting-pull-requests"},{"type":"link","label":"Review a Pull Request","href":"/Detox/docs/contributing/code/reviewing-pull-requests","docId":"contributing/code/reviewing-pull-requests"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Documentation Site","href":"/Detox/docs/contributing/documentation","docId":"contributing/documentation"},{"type":"link","label":"Code of Conduct","href":"/Detox/docs/contributing/code-of-conduct","docId":"contributing/code-of-conduct"}]},"docs":{"api/actions":{"id":"api/actions","title":"Actions","description":"Detox uses matchers to find UI elements in your app and actions to simulate user interaction with those elements.","sidebar":"apiSidebar"},"api/copilot":{"id":"api/copilot","title":"Detox Copilot","description":"Detox Copilot is an AI-powered plugin that allows you to write Detox tests using natural language commands, powered by large language models (LLMs). It simplifies the process of writing end-to-end tests by translating human-readable instructions into Detox actions and assertions.","sidebar":"apiSidebar"},"api/device":{"id":"api/device","title":"Device","description":"The device object is globally available in every test file, unless you use exposeGlobals: false in the behavior config,","sidebar":"apiSidebar"},"api/expect":{"id":"api/expect","title":"Expect","description":"Detox uses matchers to match UI elements in your app and expectations to verify those elements are in the expected state.","sidebar":"apiSidebar"},"api/internals":{"id":"api/internals","title":"Internals API","description":"This section might be more volatile than the other ones, yet we\'ll do our best to adhere to Semantic Release standards even here.","sidebar":"apiSidebar"},"api/logger":{"id":"api/logger","title":"Logger","description":"Detox Logger API allows you to save your custom messages and events alongside the built-in ones.","sidebar":"apiSidebar"},"api/matchers":{"id":"api/matchers","title":"Matchers","description":"Detox uses matchers to match UI elements in your app.","sidebar":"apiSidebar"},"api/system":{"id":"api/system","title":"System","description":"System APIs allows you to interact with dialogs in the system-level (e.g. permissions, alerts, etc.).","sidebar":"apiSidebar"},"api/webviews":{"id":"api/webviews","title":"Web Views","description":"Web views are native components that render content not natively supported by the platform, like web pages or PDF documents.","sidebar":"apiSidebar"},"articles/design-principles":{"id":"articles/design-principles","title":"Design Principles","description":"Traditionally, end-to-end tests on mobile are riddled with inherent issues, making the testing process difficult and lowering ROI for developers. We believe that the only way to solve these issues at the core is by changing some of the basic principles of our approach.","sidebar":"apiSidebar"},"articles/how-detox-works":{"id":"articles/how-detox-works","title":"How Detox Works","description":"Detox is an end-to-end testing framework. This means it runs your app on an actual device/simulator and interacts with it just like a real user would. This type of testing can give a lot of confidence in your app and help automate a manual QA process.","sidebar":"apiSidebar"},"articles/third-party-drivers":{"id":"articles/third-party-drivers","title":"Third-Party Drivers","description":"Detox comes with built-in support for running on Android and iOS by choosing a driver type in your Detox configurations.","sidebar":"apiSidebar"},"cli/build":{"id":"cli/build","title":"detox build","description":"detox build [options]","sidebar":"apiSidebar"},"cli/build-framework-cache":{"id":"cli/build-framework-cache","title":"detox build-framework-cache","description":"MacOS only.","sidebar":"apiSidebar"},"cli/clean-framework-cache":{"id":"cli/clean-framework-cache","title":"detox clean-framework-cache","description":"MacOS only.","sidebar":"apiSidebar"},"cli/init":{"id":"cli/init","title":"detox init","description":"Creates a few template files in the current project directory to get you started with Detox:","sidebar":"apiSidebar"},"cli/overview":{"id":"cli/overview","title":"detox","description":"Detox CLI lets you operate Detox from command line.","sidebar":"apiSidebar"},"cli/rebuild-framework-cache":{"id":"cli/rebuild-framework-cache","title":"detox rebuild-framework-cache","description":"MacOS only.","sidebar":"apiSidebar"},"cli/recorder":{"id":"cli/recorder","title":"detox recorder","description":"Detox Recorder tool is deprecated due to shortage of human resources in Detox team.","sidebar":"apiSidebar"},"cli/reset-lock-file":{"id":"cli/reset-lock-file","title":"detox reset-lock-file","description":"Resets Detox lock file. The lock file contains information about busy and free devices, and this way we can ensure no device can be used simultaneously by multiple Detox test sessions.","sidebar":"apiSidebar"},"cli/run-server":{"id":"cli/run-server","title":"detox run-server","description":"This tool is useful mostly for contributing to the native codebase of Detox, not for the outside use.","sidebar":"apiSidebar"},"cli/start":{"id":"cli/start","title":"detox start","description":"detox start [options]","sidebar":"apiSidebar"},"cli/test":{"id":"cli/test","title":"detox test","description":"For the most part, detox test is a convenience method which converts CLI arguments to environment variables and","sidebar":"apiSidebar"},"config/apps":{"id":"config/apps","title":"Apps","description":"The format of Detox config allows you to define inside it multiple app configs in a key-value manner, i.e.:","sidebar":"apiSidebar"},"config/artifacts":{"id":"config/artifacts","title":"Artifacts","description":"This article is incomplete. We\'re looking forward to improve this article as soon as we have an opportunity to do so.","sidebar":"apiSidebar"},"config/behavior":{"id":"config/behavior","title":"Behavior","description":"If you need to tweak the flow of detox.init() or detox.cleanup() steps,","sidebar":"apiSidebar"},"config/devices":{"id":"config/devices","title":"Devices","description":"The format of Detox config allows you to define inside it multiple device configs in a key-value manner, i.e.:","sidebar":"apiSidebar"},"config/logger":{"id":"config/logger","title":"Logger","description":"The logger section controls how the printed logs are going to look like in your terminal window.","sidebar":"apiSidebar"},"config/overview":{"id":"config/overview","title":"Overview","description":"If you prefer to read TypeScript files instead of docs, feel free to browse through","sidebar":"apiSidebar"},"config/session":{"id":"config/session","title":"Session","description":"It is not recommended to customize this section unless you are debugging native code","sidebar":"apiSidebar"},"config/testRunner":{"id":"config/testRunner","title":"Test runner","description":"While Detox was created to test mobile applications, effectively it is not a test runner \u2013 instead, it runs on top of a test runner. There are many third-party solutions for running tests, so we\'re happy to not reinvent the wheel and to devote our time to the mobile domain itself.","sidebar":"apiSidebar"},"contributing":{"id":"contributing","title":"Contributing to Detox","description":"Detox, an open-source project, greatly values community involvement. Whether you\'re a mobile developer, QA specialist, or an open source enthusiast, your contribution could significantly enhance the reliability, user experience, and development process of mobile applications.","sidebar":"contributeSidebar"},"contributing/code-of-conduct":{"id":"contributing/code-of-conduct","title":"Code of Conduct","description":"This Code of Conduct is adapted from the [Contributor Covenant],","sidebar":"contributeSidebar"},"contributing/code/building-and-testing":{"id":"contributing/code/building-and-testing","title":"Building and Testing","description":"Our JavaScript code is thoroughly verified with comprehensive unit tests, complemented by integration tests.","sidebar":"contributeSidebar"},"contributing/code/example-projects":{"id":"contributing/code/example-projects","title":"Example Projects","description":"Explore various example projects hosted in this monorepo to understand real-world usage and testing with Detox.","sidebar":"contributeSidebar"},"contributing/code/overview":{"id":"contributing/code/overview","title":"Code Changes Overview","description":"Welcome to the code changes section! As a contributor, it\'s essential to understand the project\'s goals and adhere to its code of conduct. Before contributing, please review any existing issues related to your work, ensure your code is well-documented, and has adequate test coverage. It\'s also important that your code is compatible with the project\'s supported platforms and their versions.","sidebar":"contributeSidebar"},"contributing/code/reviewing-pull-requests":{"id":"contributing/code/reviewing-pull-requests","title":"Review a Pull Request","description":"Reviewing a PR is an important part of the contribution process. It ensures the quality of the codebase and provides an opportunity for the community to collaborate and learn from each other.","sidebar":"contributeSidebar"},"contributing/code/setting-up-the-dev-environment":{"id":"contributing/code/setting-up-the-dev-environment","title":"Setting up the Development Environment","description":"This document guides you through setting up your development environment to start contributing to our codebase.","sidebar":"contributeSidebar"},"contributing/code/submitting-pull-requests":{"id":"contributing/code/submitting-pull-requests","title":"Submitting Pull Requests","description":"Before creating a PR, it\'s recommended to consult with the Detox collaborators. Request a design review or assistance with planning the tests to ensure alignment with project goals.","sidebar":"contributeSidebar"},"contributing/documentation":{"id":"contributing/documentation","title":"Documentation Site","description":"Contributions towards enhancing our documentation are highly valued in the Detox community.","sidebar":"contributeSidebar"},"contributing/feature-requests":{"id":"contributing/feature-requests","title":"Feedback and Suggestions","description":"Open dialogue and feedback, particularly feature requests, play an invaluable role in open-source communities like Detox. They provide insights into user preferences and requirements, aiding in the refinement and enhancement of the project.","sidebar":"contributeSidebar"},"contributing/questions/answering-questions":{"id":"contributing/questions/answering-questions","title":"Answering Questions","description":"Contributing answers is an invaluable part of supporting the Detox community. By sharing your knowledge and experience, you help others learn and grow, and you also contribute to enhancing Detox for the entire community.","sidebar":"contributeSidebar"},"contributing/questions/asking-questions":{"id":"contributing/questions/asking-questions","title":"Asking Questions","description":"Posting queries on public forums increases visibility and chances of getting an answer, and it also helps others with similar issues. However, it\'s essential to ask questions the right way to get the right answers.","sidebar":"contributeSidebar"},"contributing/reporting-bugs":{"id":"contributing/reporting-bugs","title":"Reporting Bugs","description":"Encountering a bug? Your detailed report is key for us to identify and rectify the issue.","sidebar":"contributeSidebar"},"copilot/best-practices":{"id":"copilot/best-practices","title":"Detox Copilot Best Practices","description":"Detox Copilot allows you to write tests using natural language commands. Each step corresponds to a specific action or assertion within your app. In case you\'re wondering how to make the most out of this feature, here are some best practices to follow when writing your Copilot intents.","sidebar":"tutorialSidebar"},"copilot/technical-overview":{"id":"copilot/technical-overview","title":"Technical Overview","description":"Detox Copilot integrates seamlessly with your testing environment by combining natural language processing with Detox\'s robust testing capabilities.","sidebar":"tutorialSidebar"},"copilot/testing-with-copilot":{"id":"copilot/testing-with-copilot","title":"Natural Language Testing with Detox Copilot","description":"In this tutorial, we\'ll explore how to use Detox Copilot to write end-to-end tests using natural language commands. Detox Copilot leverages large language models (LLMs) to translate human-readable instructions into Detox actions and assertions, making test writing more intuitive and accessible.","sidebar":"tutorialSidebar"},"demo":{"id":"demo","title":"Internal Demo Page for Styling","description":"This page is for internal use only. If you are a contributor to the Detox project, you can use this page to test your changes to the website documentation pages"},"guide/android-dev-env":{"id":"guide/android-dev-env","title":"Setting Up an Android Development & Testing Environment","description":"This guide provides some core practices to follow in setting up a stable, reliable environment for running automated UI tests using Android emulators (using Detox, in particular) \u2013 be it on a personal, local computer, or a powerful CI machine.","sidebar":"tutorialSidebar"},"guide/cucumber-js-integration":{"id":"guide/cucumber-js-integration","title":"Cucumber.js Integration","description":"This is an article generously contributed by the community.","sidebar":"tutorialSidebar"},"guide/developing-while-writing-tests":{"id":"guide/developing-while-writing-tests","title":"Developing Your App While Writing Tests","description":"If your app requires active development, such as adding testID fields for tests, this is a good workflow. It allows you to work both on your app and your tests at the same time.","sidebar":"tutorialSidebar"},"guide/genymotion-saas":{"id":"guide/genymotion-saas","title":"Using Genymotion SaaS","description":"As the number of your end-to-end tests grows, the overall test session duration might easily surpass an hour or two.","sidebar":"tutorialSidebar"},"guide/investigating-test-failure":{"id":"guide/investigating-test-failure","title":"Investigating Failures","description":"There are a few tricks and tools that can help you to understand the reason for test failures, even before you resort to debugging.","sidebar":"tutorialSidebar"},"guide/launch-args":{"id":"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).","sidebar":"tutorialSidebar"},"guide/migration":{"id":"guide/migration","title":"Migration Guide","description":"We are improving Detox API as we go along, sometimes these changes require us to break the API in order for it to make more sense. These migration guides refer to breaking changes. If a newer version has no entries in this document, it means it does not require special migration steps. Refer to the release notes of the latter builds to learn about their improvements and changes.","sidebar":"tutorialSidebar"},"guide/mocking":{"id":"guide/mocking","title":"Mocking","description":"This article previously focused on the older React Native versions (<0.59), so if you need to access it, follow this Git history link.","sidebar":"tutorialSidebar"},"guide/mocking-open-with-url":{"id":"guide/mocking-open-with-url","title":"Mocking Open With URL (Deep Links)","description":"You can mock opening the app from URL to test your app\u2019s deep link handling mechanism.","sidebar":"tutorialSidebar"},"guide/mocking-user-activity":{"id":"guide/mocking-user-activity","title":"Mocking User Activity","description":"Detox supports mocking user activity for iOS apps.","sidebar":"tutorialSidebar"},"guide/mocking-user-notifications":{"id":"guide/mocking-user-notifications","title":"Mocking User Notifications","description":"Detox supports mocking user notifications.","sidebar":"tutorialSidebar"},"guide/parallel-test-execution":{"id":"guide/parallel-test-execution","title":"Parallel Test Execution","description":"Detox comes out of the box with multi-worker support thanks to (Jest\'s feature, etc.).","sidebar":"tutorialSidebar"},"guide/proguard-configuration":{"id":"guide/proguard-configuration","title":"ProGuard configuration","description":"You can skip this guide if you are working solely with debug builds (android.emu.debug, etc.),","sidebar":"tutorialSidebar"},"guide/taking-screenshots":{"id":"guide/taking-screenshots","title":"Taking Screenshots","description":"Detox supports taking in-test screenshots of the device, making the result immediately available in the form of an image file.","sidebar":"tutorialSidebar"},"guide/test-id":{"id":"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.","sidebar":"tutorialSidebar"},"guide/testing-webviews":{"id":"guide/testing-webviews","title":"Testing WebViews","description":"In this tutorial, we\'ll go over how you can test a WebView in React Native applications using Detox. We will cover how to engage with web elements in both single and multi WebView scenarios, apply matchers, and execute actions.","sidebar":"tutorialSidebar"},"guide/typescript":{"id":"guide/typescript","title":"Using TypeScript","description":"This guide assumes you are using Detox\'s default test runner integration with Jest.","sidebar":"tutorialSidebar"},"guide/uninstalling":{"id":"guide/uninstalling","title":"Uninstalling Detox","description":"Installing and using Detox implies certain side effects: cloned devices, cache files, state files, temporary files, etc.","sidebar":"tutorialSidebar"},"introduction/debugging":{"id":"introduction/debugging","title":"How to Debug","description":"Detox Tests","sidebar":"tutorialSidebar"},"introduction/environment-setup":{"id":"introduction/environment-setup","title":"Environment Setup","description":"The Introduction section walks you through setting up Detox in your project, one step at a time.","sidebar":"tutorialSidebar"},"introduction/getting-started":{"id":"introduction/getting-started","title":"Getting Started","description":"Before You Start","sidebar":"tutorialSidebar"},"introduction/preparing-for-ci":{"id":"introduction/preparing-for-ci","title":"Preparing for CI","description":"This guide is outdated.","sidebar":"tutorialSidebar"},"introduction/project-setup":{"id":"introduction/project-setup","title":"Project Setup","description":"This article mainly covers standard React Native projects.","sidebar":"tutorialSidebar"},"introduction/your-first-test":{"id":"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","sidebar":"tutorialSidebar"},"troubleshooting/artifacts":{"id":"troubleshooting/artifacts","title":"Artifacts","description":"Video Recording Issues on CI"},"troubleshooting/building-the-app":{"id":"troubleshooting/building-the-app","title":"Dealing With Problems With Building the App & Detox","description":"This page is about issues related to building the app, typically triggered when running detox build (and not detox test, for example).","sidebar":"tutorialSidebar"},"troubleshooting/flakiness":{"id":"troubleshooting/flakiness","title":"Dealing With Flakiness in Tests","description":"What is a flaky test?","sidebar":"tutorialSidebar"},"troubleshooting/running-tests":{"id":"troubleshooting/running-tests","title":"Dealing With Problems With Running Tests","description":"This page is about issues related to executing your Detox tests, typically triggered when running detox test (and not detox build, for example).","sidebar":"tutorialSidebar"},"troubleshooting/synchronization":{"id":"troubleshooting/synchronization","title":"Dealing With Synchronization Issues in Tests","description":"Traditionally, one of the most difficult aspects of E2E testing is synchronizing the test scenario with the app. Complex operations inside the app (like accessing servers or performing animations) often take variable amount of time to complete. We can\u2019t continue the test until they\u2019ve completed. How can we synchronize the test with these operations?","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/d350cff3.5d62f296.js b/assets/js/d350cff3.5d62f296.js deleted file mode 100644 index c13034d2d9..0000000000 --- a/assets/js/d350cff3.5d62f296.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[2525],{84563:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"20.x","label":"20.x","banner":null,"badge":true,"noIndex":false,"className":"docs-version-20.x","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"category","label":"Introduction","items":[{"type":"link","label":"Getting Started","href":"/Detox/docs/introduction/getting-started","docId":"introduction/getting-started"},{"type":"link","label":"Environment Setup","href":"/Detox/docs/introduction/environment-setup","docId":"introduction/environment-setup"},{"type":"link","label":"Project Setup","href":"/Detox/docs/introduction/project-setup","docId":"introduction/project-setup"},{"type":"link","label":"Your First Test","href":"/Detox/docs/introduction/your-first-test","docId":"introduction/your-first-test"},{"type":"link","label":"How to Debug","href":"/Detox/docs/introduction/debugging","docId":"introduction/debugging"},{"type":"link","label":"Preparing for CI","href":"/Detox/docs/introduction/preparing-for-ci","docId":"introduction/preparing-for-ci"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Guides","items":[{"type":"link","label":"Investigating Failures","href":"/Detox/docs/guide/investigating-test-failure","docId":"guide/investigating-test-failure"},{"type":"link","label":"Adding test ID\'s to your components","href":"/Detox/docs/guide/test-id","docId":"guide/test-id"},{"type":"link","label":"Parallel Test Execution","href":"/Detox/docs/guide/parallel-test-execution","docId":"guide/parallel-test-execution"},{"type":"link","label":"Using TypeScript","href":"/Detox/docs/guide/typescript","docId":"guide/typescript"},{"type":"link","label":"Testing WebViews","href":"/Detox/docs/guide/testing-webviews","docId":"guide/testing-webviews"},{"type":"link","label":"Using Genymotion SaaS","href":"/Detox/docs/guide/genymotion-saas","docId":"guide/genymotion-saas"},{"type":"link","label":"Taking Screenshots","href":"/Detox/docs/guide/taking-screenshots","docId":"guide/taking-screenshots"},{"type":"link","label":"Mocking","href":"/Detox/docs/guide/mocking","docId":"guide/mocking"},{"type":"link","label":"Using Launch Arguments","href":"/Detox/docs/guide/launch-args","docId":"guide/launch-args"},{"type":"link","label":"Mocking Open With URL (Deep Links)","href":"/Detox/docs/guide/mocking-open-with-url","docId":"guide/mocking-open-with-url"},{"type":"link","label":"Mocking User Notifications","href":"/Detox/docs/guide/mocking-user-notifications","docId":"guide/mocking-user-notifications"},{"type":"link","label":"Mocking User Activity","href":"/Detox/docs/guide/mocking-user-activity","docId":"guide/mocking-user-activity"},{"type":"link","label":"Developing Your App While Writing Tests","href":"/Detox/docs/guide/developing-while-writing-tests","docId":"guide/developing-while-writing-tests"},{"type":"link","label":"Setting Up an Android Development & Testing Environment","href":"/Detox/docs/guide/android-dev-env","docId":"guide/android-dev-env"},{"type":"link","label":"ProGuard configuration","href":"/Detox/docs/guide/proguard-configuration","docId":"guide/proguard-configuration"},{"type":"link","label":"Cucumber.js Integration","href":"/Detox/docs/guide/cucumber-js-integration","docId":"guide/cucumber-js-integration"},{"type":"link","label":"Uninstalling Detox","href":"/Detox/docs/guide/uninstalling","docId":"guide/uninstalling"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Troubleshooting","items":[{"type":"link","label":"Dealing With Problems With Building the App & Detox","href":"/Detox/docs/troubleshooting/building-the-app","docId":"troubleshooting/building-the-app"},{"type":"link","label":"Dealing With Problems With Running Tests","href":"/Detox/docs/troubleshooting/running-tests","docId":"troubleshooting/running-tests"},{"type":"link","label":"Dealing With Synchronization Issues in Tests","href":"/Detox/docs/troubleshooting/synchronization","docId":"troubleshooting/synchronization"},{"type":"link","label":"Dealing With Flakiness in Tests","href":"/Detox/docs/troubleshooting/flakiness","docId":"troubleshooting/flakiness"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Migration Guide","href":"/Detox/docs/guide/migration","docId":"guide/migration"}],"apiSidebar":[{"type":"category","label":"Config file","items":[{"type":"link","label":"Overview","href":"/Detox/docs/config/overview","docId":"config/overview"},{"type":"link","label":"Devices","href":"/Detox/docs/config/devices","docId":"config/devices"},{"type":"link","label":"Apps","href":"/Detox/docs/config/apps","docId":"config/apps"},{"type":"link","label":"Artifacts","href":"/Detox/docs/config/artifacts","docId":"config/artifacts"},{"type":"link","label":"Behavior","href":"/Detox/docs/config/behavior","docId":"config/behavior"},{"type":"link","label":"Logger","href":"/Detox/docs/config/logger","docId":"config/logger"},{"type":"link","label":"Session","href":"/Detox/docs/config/session","docId":"config/session"},{"type":"link","label":"Test runner","href":"/Detox/docs/config/testRunner","docId":"config/testRunner"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Command Line Interface","items":[{"type":"link","label":"detox","href":"/Detox/docs/cli/overview","docId":"cli/overview"},{"type":"link","label":"detox init","href":"/Detox/docs/cli/init","docId":"cli/init"},{"type":"link","label":"detox build","href":"/Detox/docs/cli/build","docId":"cli/build"},{"type":"link","label":"detox start","href":"/Detox/docs/cli/start","docId":"cli/start"},{"type":"link","label":"detox test","href":"/Detox/docs/cli/test","docId":"cli/test"},{"type":"link","label":"detox recorder","href":"/Detox/docs/cli/recorder","docId":"cli/recorder"},{"type":"link","label":"detox build-framework-cache","href":"/Detox/docs/cli/build-framework-cache","docId":"cli/build-framework-cache"},{"type":"link","label":"detox clean-framework-cache","href":"/Detox/docs/cli/clean-framework-cache","docId":"cli/clean-framework-cache"},{"type":"link","label":"detox rebuild-framework-cache","href":"/Detox/docs/cli/rebuild-framework-cache","docId":"cli/rebuild-framework-cache"},{"type":"link","label":"detox reset-lock-file","href":"/Detox/docs/cli/reset-lock-file","docId":"cli/reset-lock-file"},{"type":"link","label":"detox run-server","href":"/Detox/docs/cli/run-server","docId":"cli/run-server"}],"collapsed":true,"collapsible":true},{"type":"category","label":"Client API","items":[{"type":"link","label":"Device","href":"/Detox/docs/api/device","docId":"api/device"},{"type":"link","label":"Matchers","href":"/Detox/docs/api/matchers","docId":"api/matchers"},{"type":"link","label":"Actions","href":"/Detox/docs/api/actions","docId":"api/actions"},{"type":"link","label":"Expect","href":"/Detox/docs/api/expect","docId":"api/expect"},{"type":"link","label":"Web Views","href":"/Detox/docs/api/webviews","docId":"api/webviews"},{"type":"link","label":"System","href":"/Detox/docs/api/system","docId":"api/system"},{"type":"link","label":"Logger","href":"/Detox/docs/api/logger","docId":"api/logger"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Internals API","href":"/Detox/docs/api/internals","docId":"api/internals"},{"type":"category","label":"Tech Articles","items":[{"type":"link","label":"Design Principles","href":"/Detox/docs/articles/design-principles","docId":"articles/design-principles"},{"type":"link","label":"How Detox Works","href":"/Detox/docs/articles/how-detox-works","docId":"articles/how-detox-works"},{"type":"link","label":"Third-Party Drivers","href":"/Detox/docs/articles/third-party-drivers","docId":"articles/third-party-drivers"}],"collapsed":true,"collapsible":true}],"contributeSidebar":[{"type":"link","label":"Contributing to Detox","href":"/Detox/docs/contributing","docId":"contributing"},{"type":"category","label":"Questions & Answers","items":[{"type":"link","label":"Asking Questions","href":"/Detox/docs/contributing/questions/asking-questions","docId":"contributing/questions/asking-questions"},{"type":"link","label":"Answering Questions","href":"/Detox/docs/contributing/questions/answering-questions","docId":"contributing/questions/answering-questions"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Reporting Bugs","href":"/Detox/docs/contributing/reporting-bugs","docId":"contributing/reporting-bugs"},{"type":"link","label":"Feedback and Suggestions","href":"/Detox/docs/contributing/feature-requests","docId":"contributing/feature-requests"},{"type":"category","label":"Code Changes","items":[{"type":"link","label":"Overview","href":"/Detox/docs/contributing/code/overview","docId":"contributing/code/overview"},{"type":"link","label":"Setting up the Development Environment","href":"/Detox/docs/contributing/code/setting-up-the-dev-environment","docId":"contributing/code/setting-up-the-dev-environment"},{"type":"link","label":"Building and Testing","href":"/Detox/docs/contributing/code/building-and-testing","docId":"contributing/code/building-and-testing"},{"type":"link","label":"Example Projects","href":"/Detox/docs/contributing/code/example-projects","docId":"contributing/code/example-projects"},{"type":"link","label":"Submitting Pull Requests","href":"/Detox/docs/contributing/code/submitting-pull-requests","docId":"contributing/code/submitting-pull-requests"},{"type":"link","label":"Review a Pull Request","href":"/Detox/docs/contributing/code/reviewing-pull-requests","docId":"contributing/code/reviewing-pull-requests"}],"collapsed":true,"collapsible":true},{"type":"link","label":"Documentation Site","href":"/Detox/docs/contributing/documentation","docId":"contributing/documentation"},{"type":"link","label":"Code of Conduct","href":"/Detox/docs/contributing/code-of-conduct","docId":"contributing/code-of-conduct"}]},"docs":{"api/actions":{"id":"api/actions","title":"Actions","description":"Detox uses matchers to find UI elements in your app and actions to simulate user interaction with those elements.","sidebar":"apiSidebar"},"api/device":{"id":"api/device","title":"Device","description":"The device object is globally available in every test file, unless you use exposeGlobals: false in the behavior config,","sidebar":"apiSidebar"},"api/expect":{"id":"api/expect","title":"Expect","description":"Detox uses matchers to match UI elements in your app and expectations to verify those elements are in the expected state.","sidebar":"apiSidebar"},"api/internals":{"id":"api/internals","title":"Internals API","description":"This section might be more volatile than the other ones, yet we\'ll do our best to adhere to Semantic Release standards even here.","sidebar":"apiSidebar"},"api/logger":{"id":"api/logger","title":"Logger","description":"Detox Logger API allows you to save your custom messages and events alongside the built-in ones.","sidebar":"apiSidebar"},"api/matchers":{"id":"api/matchers","title":"Matchers","description":"Detox uses matchers to match UI elements in your app.","sidebar":"apiSidebar"},"api/system":{"id":"api/system","title":"System","description":"System APIs allows you to interact with dialogs in the system-level (e.g. permissions, alerts, etc.).","sidebar":"apiSidebar"},"api/webviews":{"id":"api/webviews","title":"Web Views","description":"Web views are native components that render content not natively supported by the platform, like web pages or PDF documents.","sidebar":"apiSidebar"},"articles/design-principles":{"id":"articles/design-principles","title":"Design Principles","description":"Traditionally, end-to-end tests on mobile are riddled with inherent issues, making the testing process difficult and lowering ROI for developers. We believe that the only way to solve these issues at the core is by changing some of the basic principles of our approach.","sidebar":"apiSidebar"},"articles/how-detox-works":{"id":"articles/how-detox-works","title":"How Detox Works","description":"Detox is an end-to-end testing framework. This means it runs your app on an actual device/simulator and interacts with it just like a real user would. This type of testing can give a lot of confidence in your app and help automate a manual QA process.","sidebar":"apiSidebar"},"articles/third-party-drivers":{"id":"articles/third-party-drivers","title":"Third-Party Drivers","description":"Detox comes with built-in support for running on Android and iOS by choosing a driver type in your Detox configurations.","sidebar":"apiSidebar"},"cli/build":{"id":"cli/build","title":"detox build","description":"detox build [options]","sidebar":"apiSidebar"},"cli/build-framework-cache":{"id":"cli/build-framework-cache","title":"detox build-framework-cache","description":"MacOS only.","sidebar":"apiSidebar"},"cli/clean-framework-cache":{"id":"cli/clean-framework-cache","title":"detox clean-framework-cache","description":"MacOS only.","sidebar":"apiSidebar"},"cli/init":{"id":"cli/init","title":"detox init","description":"Creates a few template files in the current project directory to get you started with Detox:","sidebar":"apiSidebar"},"cli/overview":{"id":"cli/overview","title":"detox","description":"Detox CLI lets you operate Detox from command line.","sidebar":"apiSidebar"},"cli/rebuild-framework-cache":{"id":"cli/rebuild-framework-cache","title":"detox rebuild-framework-cache","description":"MacOS only.","sidebar":"apiSidebar"},"cli/recorder":{"id":"cli/recorder","title":"detox recorder","description":"Detox Recorder tool is deprecated due to shortage of human resources in Detox team.","sidebar":"apiSidebar"},"cli/reset-lock-file":{"id":"cli/reset-lock-file","title":"detox reset-lock-file","description":"Resets Detox lock file. The lock file contains information about busy and free devices, and this way we can ensure no device can be used simultaneously by multiple Detox test sessions.","sidebar":"apiSidebar"},"cli/run-server":{"id":"cli/run-server","title":"detox run-server","description":"This tool is useful mostly for contributing to the native codebase of Detox, not for the outside use.","sidebar":"apiSidebar"},"cli/start":{"id":"cli/start","title":"detox start","description":"detox start [options]","sidebar":"apiSidebar"},"cli/test":{"id":"cli/test","title":"detox test","description":"For the most part, detox test is a convenience method which converts CLI arguments to environment variables and","sidebar":"apiSidebar"},"config/apps":{"id":"config/apps","title":"Apps","description":"The format of Detox config allows you to define inside it multiple app configs in a key-value manner, i.e.:","sidebar":"apiSidebar"},"config/artifacts":{"id":"config/artifacts","title":"Artifacts","description":"This article is incomplete. We\'re looking forward to improve this article as soon as we have an opportunity to do so.","sidebar":"apiSidebar"},"config/behavior":{"id":"config/behavior","title":"Behavior","description":"If you need to tweak the flow of detox.init() or detox.cleanup() steps,","sidebar":"apiSidebar"},"config/devices":{"id":"config/devices","title":"Devices","description":"The format of Detox config allows you to define inside it multiple device configs in a key-value manner, i.e.:","sidebar":"apiSidebar"},"config/logger":{"id":"config/logger","title":"Logger","description":"The logger section controls how the printed logs are going to look like in your terminal window.","sidebar":"apiSidebar"},"config/overview":{"id":"config/overview","title":"Overview","description":"If you prefer to read TypeScript files instead of docs, feel free to browse through","sidebar":"apiSidebar"},"config/session":{"id":"config/session","title":"Session","description":"It is not recommended to customize this section unless you are debugging native code","sidebar":"apiSidebar"},"config/testRunner":{"id":"config/testRunner","title":"Test runner","description":"While Detox was created to test mobile applications, effectively it is not a test runner \u2013 instead, it runs on top of a test runner. There are many third-party solutions for running tests, so we\'re happy to not reinvent the wheel and to devote our time to the mobile domain itself.","sidebar":"apiSidebar"},"contributing":{"id":"contributing","title":"Contributing to Detox","description":"Detox, an open-source project, greatly values community involvement. Whether you\'re a mobile developer, QA specialist, or an open source enthusiast, your contribution could significantly enhance the reliability, user experience, and development process of mobile applications.","sidebar":"contributeSidebar"},"contributing/code-of-conduct":{"id":"contributing/code-of-conduct","title":"Code of Conduct","description":"This Code of Conduct is adapted from the [Contributor Covenant],","sidebar":"contributeSidebar"},"contributing/code/building-and-testing":{"id":"contributing/code/building-and-testing","title":"Building and Testing","description":"Our JavaScript code is thoroughly verified with comprehensive unit tests, complemented by integration tests.","sidebar":"contributeSidebar"},"contributing/code/example-projects":{"id":"contributing/code/example-projects","title":"Example Projects","description":"Explore various example projects hosted in this monorepo to understand real-world usage and testing with Detox.","sidebar":"contributeSidebar"},"contributing/code/overview":{"id":"contributing/code/overview","title":"Code Changes Overview","description":"Welcome to the code changes section! As a contributor, it\'s essential to understand the project\'s goals and adhere to its code of conduct. Before contributing, please review any existing issues related to your work, ensure your code is well-documented, and has adequate test coverage. It\'s also important that your code is compatible with the project\'s supported platforms and their versions.","sidebar":"contributeSidebar"},"contributing/code/reviewing-pull-requests":{"id":"contributing/code/reviewing-pull-requests","title":"Review a Pull Request","description":"Reviewing a PR is an important part of the contribution process. It ensures the quality of the codebase and provides an opportunity for the community to collaborate and learn from each other.","sidebar":"contributeSidebar"},"contributing/code/setting-up-the-dev-environment":{"id":"contributing/code/setting-up-the-dev-environment","title":"Setting up the Development Environment","description":"This document guides you through setting up your development environment to start contributing to our codebase.","sidebar":"contributeSidebar"},"contributing/code/submitting-pull-requests":{"id":"contributing/code/submitting-pull-requests","title":"Submitting Pull Requests","description":"Before creating a PR, it\'s recommended to consult with the Detox collaborators. Request a design review or assistance with planning the tests to ensure alignment with project goals.","sidebar":"contributeSidebar"},"contributing/documentation":{"id":"contributing/documentation","title":"Documentation Site","description":"Contributions towards enhancing our documentation are highly valued in the Detox community.","sidebar":"contributeSidebar"},"contributing/feature-requests":{"id":"contributing/feature-requests","title":"Feedback and Suggestions","description":"Open dialogue and feedback, particularly feature requests, play an invaluable role in open-source communities like Detox. They provide insights into user preferences and requirements, aiding in the refinement and enhancement of the project.","sidebar":"contributeSidebar"},"contributing/questions/answering-questions":{"id":"contributing/questions/answering-questions","title":"Answering Questions","description":"Contributing answers is an invaluable part of supporting the Detox community. By sharing your knowledge and experience, you help others learn and grow, and you also contribute to enhancing Detox for the entire community.","sidebar":"contributeSidebar"},"contributing/questions/asking-questions":{"id":"contributing/questions/asking-questions","title":"Asking Questions","description":"Posting queries on public forums increases visibility and chances of getting an answer, and it also helps others with similar issues. However, it\'s essential to ask questions the right way to get the right answers.","sidebar":"contributeSidebar"},"contributing/reporting-bugs":{"id":"contributing/reporting-bugs","title":"Reporting Bugs","description":"Encountering a bug? Your detailed report is key for us to identify and rectify the issue.","sidebar":"contributeSidebar"},"demo":{"id":"demo","title":"Internal Demo Page for Styling","description":"This page is for internal use only. If you are a contributor to the Detox project, you can use this page to test your changes to the website documentation pages"},"guide/android-dev-env":{"id":"guide/android-dev-env","title":"Setting Up an Android Development & Testing Environment","description":"This guide provides some core practices to follow in setting up a stable, reliable environment for running automated UI tests using Android emulators (using Detox, in particular) \u2013 be it on a personal, local computer, or a powerful CI machine.","sidebar":"tutorialSidebar"},"guide/cucumber-js-integration":{"id":"guide/cucumber-js-integration","title":"Cucumber.js Integration","description":"This is an article generously contributed by the community.","sidebar":"tutorialSidebar"},"guide/developing-while-writing-tests":{"id":"guide/developing-while-writing-tests","title":"Developing Your App While Writing Tests","description":"If your app requires active development, such as adding testID fields for tests, this is a good workflow. It allows you to work both on your app and your tests at the same time.","sidebar":"tutorialSidebar"},"guide/genymotion-saas":{"id":"guide/genymotion-saas","title":"Using Genymotion SaaS","description":"As the number of your end-to-end tests grows, the overall test session duration might easily surpass an hour or two.","sidebar":"tutorialSidebar"},"guide/investigating-test-failure":{"id":"guide/investigating-test-failure","title":"Investigating Failures","description":"There are a few tricks and tools that can help you to understand the reason for test failures, even before you resort to debugging.","sidebar":"tutorialSidebar"},"guide/launch-args":{"id":"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).","sidebar":"tutorialSidebar"},"guide/migration":{"id":"guide/migration","title":"Migration Guide","description":"We are improving Detox API as we go along, sometimes these changes require us to break the API in order for it to make more sense. These migration guides refer to breaking changes. If a newer version has no entries in this document, it means it does not require special migration steps. Refer to the release notes of the latter builds to learn about their improvements and changes.","sidebar":"tutorialSidebar"},"guide/mocking":{"id":"guide/mocking","title":"Mocking","description":"This article previously focused on the older React Native versions (<0.59), so if you need to access it, follow this Git history link.","sidebar":"tutorialSidebar"},"guide/mocking-open-with-url":{"id":"guide/mocking-open-with-url","title":"Mocking Open With URL (Deep Links)","description":"You can mock opening the app from URL to test your app\u2019s deep link handling mechanism.","sidebar":"tutorialSidebar"},"guide/mocking-user-activity":{"id":"guide/mocking-user-activity","title":"Mocking User Activity","description":"Detox supports mocking user activity for iOS apps.","sidebar":"tutorialSidebar"},"guide/mocking-user-notifications":{"id":"guide/mocking-user-notifications","title":"Mocking User Notifications","description":"Detox supports mocking user notifications.","sidebar":"tutorialSidebar"},"guide/parallel-test-execution":{"id":"guide/parallel-test-execution","title":"Parallel Test Execution","description":"Detox comes out of the box with multi-worker support thanks to (Jest\'s feature, etc.).","sidebar":"tutorialSidebar"},"guide/proguard-configuration":{"id":"guide/proguard-configuration","title":"ProGuard configuration","description":"You can skip this guide if you are working solely with debug builds (android.emu.debug, etc.),","sidebar":"tutorialSidebar"},"guide/taking-screenshots":{"id":"guide/taking-screenshots","title":"Taking Screenshots","description":"Detox supports taking in-test screenshots of the device, making the result immediately available in the form of an image file.","sidebar":"tutorialSidebar"},"guide/test-id":{"id":"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.","sidebar":"tutorialSidebar"},"guide/testing-webviews":{"id":"guide/testing-webviews","title":"Testing WebViews","description":"In this tutorial, we\'ll go over how you can test a WebView in React Native applications using Detox. We will cover how to engage with web elements in both single and multi WebView scenarios, apply matchers, and execute actions.","sidebar":"tutorialSidebar"},"guide/typescript":{"id":"guide/typescript","title":"Using TypeScript","description":"This guide assumes you are using Detox\'s default test runner integration with Jest.","sidebar":"tutorialSidebar"},"guide/uninstalling":{"id":"guide/uninstalling","title":"Uninstalling Detox","description":"Installing and using Detox implies certain side effects: cloned devices, cache files, state files, temporary files, etc.","sidebar":"tutorialSidebar"},"introduction/debugging":{"id":"introduction/debugging","title":"How to Debug","description":"Detox Tests","sidebar":"tutorialSidebar"},"introduction/environment-setup":{"id":"introduction/environment-setup","title":"Environment Setup","description":"The Introduction section walks you through setting up Detox in your project, one step at a time.","sidebar":"tutorialSidebar"},"introduction/getting-started":{"id":"introduction/getting-started","title":"Getting Started","description":"Before You Start","sidebar":"tutorialSidebar"},"introduction/preparing-for-ci":{"id":"introduction/preparing-for-ci","title":"Preparing for CI","description":"This guide is outdated.","sidebar":"tutorialSidebar"},"introduction/project-setup":{"id":"introduction/project-setup","title":"Project Setup","description":"This article mainly covers standard React Native projects.","sidebar":"tutorialSidebar"},"introduction/your-first-test":{"id":"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","sidebar":"tutorialSidebar"},"troubleshooting/artifacts":{"id":"troubleshooting/artifacts","title":"Artifacts","description":"Video Recording Issues on CI"},"troubleshooting/building-the-app":{"id":"troubleshooting/building-the-app","title":"Dealing With Problems With Building the App & Detox","description":"This page is about issues related to building the app, typically triggered when running detox build (and not detox test, for example).","sidebar":"tutorialSidebar"},"troubleshooting/flakiness":{"id":"troubleshooting/flakiness","title":"Dealing With Flakiness in Tests","description":"What is a flaky test?","sidebar":"tutorialSidebar"},"troubleshooting/running-tests":{"id":"troubleshooting/running-tests","title":"Dealing With Problems With Running Tests","description":"This page is about issues related to executing your Detox tests, typically triggered when running detox test (and not detox build, for example).","sidebar":"tutorialSidebar"},"troubleshooting/synchronization":{"id":"troubleshooting/synchronization","title":"Dealing With Synchronization Issues in Tests","description":"Traditionally, one of the most difficult aspects of E2E testing is synchronizing the test scenario with the app. Complex operations inside the app (like accessing servers or performing animations) often take variable amount of time to complete. We can\u2019t continue the test until they\u2019ve completed. How can we synchronize the test with these operations?","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/f98b7248.dff8ac9c.js b/assets/js/f98b7248.34446239.js similarity index 99% rename from assets/js/f98b7248.dff8ac9c.js rename to assets/js/f98b7248.34446239.js index 41d78771a9..cfbead4040 100644 --- a/assets/js/f98b7248.dff8ac9c.js +++ b/assets/js/f98b7248.34446239.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6941],{15680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>c});var o=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=o.createContext({}),p=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},u=function(e){var t=p(e.components);return o.createElement(l.Provider,{value:t},e.children)},g="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},m=o.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),g=p(n),m=a,c=g["".concat(l,".").concat(m)]||g[m]||d[m]||i;return n?o.createElement(c,r(r({ref:t},u),{},{components:n})):o.createElement(c,r({ref:t},u))}));function c(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,r=new Array(i);r[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[g]="string"==typeof e?e:a,r[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var o=n(58168),a=(n(96540),n(15680));const i={authors:["noomorph"],tags:["major-release","genymotion"]},r="Detox 20 is out",s={permalink:"/Detox/blog/2022/11/10/detox-20-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2022-11-10-detox-20-is-out.md",source:"@site/blog/2022-11-10-detox-20-is-out.md",title:"Detox 20 is out",description:'Today we\'re proud to announce the new major release, Detox 20 (codename "Ash\xe1n"), which brings:',date:"2022-11-10T00:00:00.000Z",formattedDate:"November 10, 2022",tags:[{label:"major-release",permalink:"/Detox/blog/tags/major-release"},{label:"genymotion",permalink:"/Detox/blog/tags/genymotion"}],readingTime:9.705,hasTruncateMarker:!1,authors:[{name:"Yaroslav Serhieiev",title:"Detox Core Contributor",url:"https://github.com/noomorph",imageURL:"https://github.com/noomorph.png",key:"noomorph"}],frontMatter:{authors:["noomorph"],tags:["major-release","genymotion"]},prevItem:{title:"Introducing Detox Copilot",permalink:"/Detox/blog/2024/09/30/detox-copilot-is-out"}},l={authorsImageUrls:[void 0]},p=[{value:"Genymotion SaaS",id:"genymotion-saas",level:2},{value:"Integration with test runners",id:"integration-with-test-runners",level:2},{value:"Configurable logger",id:"configurable-logger",level:2},{value:"Minor features",id:"minor-features",level:2},{value:"Headless iOS",id:"headless-ios",level:3},{value:"Reverse ports",id:"reverse-ports",level:3},{value:"Read-only emulators by default",id:"read-only-emulators-by-default",level:3},{value:"Reset lock file",id:"reset-lock-file",level:3},{value:"Deprecations",id:"deprecations",level:2},{value:"Afterword",id:"afterword",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...i}=e;return(0,a.yg)(g,(0,o.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"Today we're proud to announce the new major release, ",(0,a.yg)("strong",{parentName:"p"},"Detox 20"),' (codename "Ash\xe1n"), which brings:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"official support for Genymotion SaaS"),(0,a.yg)("li",{parentName:"ul"},"improved integration with test runners"),(0,a.yg)("li",{parentName:"ul"},"configurable logging subsystem"),(0,a.yg)("li",{parentName:"ul"},"headless mode for iOS via configs and CLI"),(0,a.yg)("li",{parentName:"ul"},"reversing TCP ports via Android app configs"),(0,a.yg)("li",{parentName:"ul"},"and more optimizations to land in the next minor versions.")),(0,a.yg)("h2",{id:"genymotion-saas"},"Genymotion SaaS"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/genymotion-saas"},(0,a.yg)("inlineCode",{parentName:"a"},"Using Genymotion SaaS")),"."),(0,a.yg)("p",null,"Two years ago we ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/2446"},"added elementary support")," for cloud-based Android emulators provided by ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," and started a beta testing phase across mobile projects at Wix."),(0,a.yg)("p",null,"Previously our mobile infrastructure engineers had been maintaining Android virtual devices on CI build agents on their own, so switching to cloud devices cleared up their time for more productive tasks. Another improvement was particularly noticeable for teams with a vast number of tests. We could reduce the duration of their CI pipelines almost by half after they scaled up from 2 parallel devices to 6",(0,a.yg)("sup",{parentName:"p",id:"fnref-1-2b8cfe"},(0,a.yg)("a",{parentName:"sup",href:"#fn-1-2b8cfe",className:"footnote-ref"},"1")),"."),(0,a.yg)("p",null,"This positive impact encouraged us to adopt ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," for CI as quickly as possible, ignoring some unresolved issues in the initial pull request. For the most part, those were minor problems in global lifecycle management. Yet that made us feel uncertain about releasing it as-is, so we decided to take time and gain more production experience before taking any direction."),(0,a.yg)("p",null,"The further experience was surprisingly smooth and rarely presented issues, spare for a few minor ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3573"},"glitches")," in advanced scenarios. Admittedly, revamping the ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Detox lifecycle")," took us longer than expected, which is all the more reason for us to celebrate today."),(0,a.yg)("p",null,"We're looking forward to providing our users with more opportunities for testing in the cloud, and this step is only the first of many to come. We hope you'll utilize this new feature to your delight."),(0,a.yg)("h2",{id:"integration-with-test-runners"},"Integration with test runners"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Test runner")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},(0,a.yg)("inlineCode",{parentName:"a"},"Internals API")),", ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3193"},(0,a.yg)("inlineCode",{parentName:"a"},"Dropping Mocha support")),"."),(0,a.yg)("p",null,"It took about a few months of work to formalize the contract between Detox and a test runner. While there's still a lot of place for improvement, the new Detox release refines their interaction and lays the groundwork for third-party integrations."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://mochajs.org"},"Mocha")," was our first supported test runner, but unfortunately, it could not keep up with our scaling requirements as the number of end-to-end tests grew. ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/mochajs/mocha/releases/tag/v8.0.0"},"By the time")," it acquired the ability to run tests in parallel, we already had ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/609"},"to place bets")," on another horse, and that was ",(0,a.yg)("a",{parentName:"p",href:"https://jestjs.io"},"Jest"),"."),(0,a.yg)("p",null,"We attempted to keep compatibility with both Jest and Mocha, but the farther we went, the more obvious it was that we couldn't have it both ways. As it turned out, Jest wasn't easy to get along with \u2013 our first integration with it was too simplistic. Over a couple of years of use in production, we kept discovering various issues that forced us to rewrite our \"glue\" code from scratch twice, and this isn't over yet. All combined didn't leave much time and energy for tinkering with Mocha anymore."),(0,a.yg)("p",null,"In this release, we discontinued Mocha support to focus on the attunement of Jest with the new runner-independent ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},"test runner config")," and ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Internals API"),". If there's enough demand, now it is up to the open-source community to build a new integration between Detox and Mocha."),(0,a.yg)("h2",{id:"configurable-logger"},"Configurable logger"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Logger")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Logger API")),"."),(0,a.yg)("p",null,"The rigidity of the logging subsystem has always been showing itself ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/835"},"since its very creation")," in the summer of 2019.\nDue to time constraints and existing tech debts, it was impossible to do it right the first time, so we lived about three years with a proof-of-concept rather than a full-fledged feature."),(0,a.yg)("p",null,"The inconveniences weren't fatal but quite noticeable, nevertheless. Here are a few syndromes you could have spotted if you have ever used Detox timeline and log artifacts, especially when running tests in parallel:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"an uncanny file array: ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log.json"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7506.log"),";"),(0,a.yg)("li",{parentName:"ul"},"a relatively shallow ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json"),": test suites, test functions, and some user-defined segments.")),(0,a.yg)("p",null,"The good news is that the new Detox release condenses all those numerous logs into two files:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"the plain, human-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.log"),";"),(0,a.yg)("li",{parentName:"ul"},"the raw, machine-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json")," for ",(0,a.yg)("inlineCode",{parentName:"li"},"chrome://trace"),", ",(0,a.yg)("a",{parentName:"li",href:"https://ui.perfetto.dev/"},"Perfetto")," and other utilities.")),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"A screenshot of timeline view generated by Perfetto",src:n(27709).A,width:"1600",height:"1000"})),(0,a.yg)("p",null,"With the help of the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},"Logger API"),", you can add custom duration events to the timeline, too, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"await detox.log.trace.complete('Login', async () => {\n await element(by.id('email')).typeText('john@example.com');\n await element(by.id('password')).typeText('123456');\n\n detox.log.info('Trying to log in...');\n await element(by.id('submit')).tap();\n});\n")),(0,a.yg)("p",null,"Besides, it is possible now to customize the console output of Detox via the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},"logger config"),", e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox.config.js"',title:'"detox.config.js"'},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n logger: {\n options: {\n showDate: false,\n showLoggerName: false,\n showPid: false,\n prefixers: {\n ph: null,\n },\n },\n },\n};\n")),(0,a.yg)("p",null,"In the example above, we minimize all the metadata around the log messages \u2013 see the screenshot below:"),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Terser logs after applying the override",src:n(72716).A,width:"1400",height:"399"})),(0,a.yg)("h2",{id:"minor-features"},"Minor features"),(0,a.yg)("h3",{id:"headless-ios"},"Headless iOS"),(0,a.yg)("p",null,"One of Detox known issues was always booting iOS simulators in a hidden mode. You could see tests running on your local simulator only if you had manually opened the Simulator app beforehand. So, we unified the ",(0,a.yg)("inlineCode",{parentName:"p"},"headless")," property for both iOS and Android, and now both the platforms visibly boot a device unless you configure it otherwise, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/* @type {Detox.DetoxConfig} */\nmodule.exports = {\n devices: {\n iphone: {\n type: 'ios.simulator',\n // highlight-next-line\n headless: process.env.CI ? true : undefined,\n device: {\n type: 'iPhone 14'\n },\n /* ... */\n }\n },\n};\n")),(0,a.yg)("p",null,"or, via CLI:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c ios.sim.release --headless\n")),(0,a.yg)("h3",{id:"reverse-ports"},"Reverse ports"),(0,a.yg)("p",null,"Your apps might try to access some ",(0,a.yg)("inlineCode",{parentName:"p"},"localhost:*")," addresses (e.g., mock servers), but this is a bit more problematic in the case of Android. The Android emulators are separate virtual devices with their own loopback network interface. In such cases, you must set up reverse port forwarding via ",(0,a.yg)("inlineCode",{parentName:"p"},"adb reverse"),"."),(0,a.yg)("p",null,"Local servers are quite a common prerequisite for apps in debug mode \u2013 one could recall React Native bundler on port 8081, Storybook server on 9009, etc. That's why we decided to add an optional config property for Android apps, ",(0,a.yg)("inlineCode",{parentName:"p"},"reversePorts"),":"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n apps: {\n 'android.debug': {\n type: 'android.apk',\n binaryPath: '...',\n reversePorts: [8081, 3000],\n },\n },\n};\n")),(0,a.yg)("p",null,"In other words, this is a convenience API that tells Detox to run ",(0,a.yg)("inlineCode",{parentName:"p"},"device.reverseTcpPort(portNumber)")," after installing the app. It should be helpful for anyone who prefers to keep such things as configs rather than as code."),(0,a.yg)("h3",{id:"read-only-emulators-by-default"},"Read-only emulators by default"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," flag appeared in ",(0,a.yg)("a",{parentName:"p",href:"https://developer.android.com/studio/releases/emulator#concurrent-avd"},"Android emulator 28.0.16"),". Detox promptly adopted it since the read-only mode allowed it to run multiple instances of a single Android virtual device (AVD) concurrently. This feature helped us to implement parallel test execution support for Android back then."),(0,a.yg)("p",null,"Being overcautious, we implemented that partially, only for cases when the user starts multiple concurrent workers. This decision created a moderately annoying UX issue. Imagine you run tests sequentially first, using one worker only. That provides you with a regular AVD instance, i.e., not a read-only one. After that, you switch to multiple workers only to get an error from the Android emulator, complaining about mixing regular and read-only instances."),(0,a.yg)("p",null,"While the fix itself has always been straightforward \u2013 close the running AVD and try again \u2013 this entire overcaution brought more issues than solving them. That's why, from now on, Android emulators will always be starting in ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," mode unless you configure ",(0,a.yg)("inlineCode",{parentName:"p"},"readonly: false")," in your ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/devices"},"device config"),"."),(0,a.yg)("h3",{id:"reset-lock-file"},"Reset lock file"),(0,a.yg)("p",null,"This release adds a small CLI tool, ",(0,a.yg)("a",{parentName:"p",href:"/docs/cli/reset-lock-file"},(0,a.yg)("inlineCode",{parentName:"a"},"detox reset-lock-file")),", to help users with one specific use scenario."),(0,a.yg)("p",null,"Imagine you want to run tests for multiple Detox configurations at once, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c iphoneSE2020.release e2e/ui.test.js\ndetox test -c iphone14ProMax.release e2e/ui.test.js\n")),(0,a.yg)("p",null,"The problem is that Detox uses a file-locking mechanism to avoid situations when parallel test workers would take control of the same device. The ",(0,a.yg)("inlineCode",{parentName:"p"},"detox test")," command, upon start, erases that file contents, creating a race condition risk."),(0,a.yg)("p",null,"To eliminate that risk, use a combination of ",(0,a.yg)("inlineCode",{parentName:"p"},"detox reset-lock-file")," and ",(0,a.yg)("inlineCode",{parentName:"p"},"--keepLockFile")," like this:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox reset-lock-file & \\\ndetox test --keepLockFile -c iphoneSE2020.release e2e/ui.test.js & \\\ndetox test --keepLockFile -c iphone14ProMax.release e2e/ui.test.js & \\\nwait\n")),(0,a.yg)("p",null,"In the future, we plan to minimize using lock files so that you don't have to think about this low-level implementation detail.\nSo, this tool adds some convenience until we provide a next-gen solution."),(0,a.yg)("h2",{id:"deprecations"},"Deprecations"),(0,a.yg)("p",null,"Detox 20 executes many pending deprecations, so make sure to check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/migration#200"},"Migration Guide")," before upgrading:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Node.js version is ",(0,a.yg)("inlineCode",{parentName:"li"},"14.x"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Jest version is ",(0,a.yg)("inlineCode",{parentName:"li"},"27.2.5"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: Mocha test runner is no longer supported;"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued old adapters for Jest (",(0,a.yg)("inlineCode",{parentName:"li"},"jest-jasmine"),", first generation of ",(0,a.yg)("inlineCode",{parentName:"li"},"jest-circus")," adapter);"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"{ permanent: true }")," option in ",(0,a.yg)("inlineCode",{parentName:"li"},"device.appLaunchArgs.*")," methods (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3360"},"#3360"),");"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped ",(0,a.yg)("inlineCode",{parentName:"li"},"-w, --workers")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"-o, --runner-config")," args \u2013 see a ",(0,a.yg)("a",{parentName:"li",href:"/docs/guide/migration#updating-command-line-scripts"},"dedicated section")," in the migration guide;"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"--device-launch-args")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3665"},"#3665"),");"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued kebab-case properties: ",(0,a.yg)("inlineCode",{parentName:"li"},"test-runner"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"runner-config")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3371"},"#3371"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"skipLegacyWorkersInjections")," property (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3286"},"(#3286)"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"specs")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"runnerConfig")," properties"),(0,a.yg)("li",{parentName:"ul"},"Config: changed ",(0,a.yg)("a",{parentName:"li",href:"/docs/config/testRunner"},"semantics")," of ",(0,a.yg)("inlineCode",{parentName:"li"},"testRunner")," property"),(0,a.yg)("li",{parentName:"ul"},"Config: dropped support for all-in-one configurations (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3386"},"#3386"),");"),(0,a.yg)("li",{parentName:"ul"},"Android: remove deprecated native IdlePolicyConfig (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3332/files"},"#3332"),")"),(0,a.yg)("li",{parentName:"ul"},"iOS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"ios.none")," device type \u2013 see the new way to ",(0,a.yg)("a",{parentName:"li",href:"/docs/introduction/debugging#native-application-code"},"debug native code")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3361"},"#3361"),")")),(0,a.yg)("h2",{id:"afterword"},"Afterword"),(0,a.yg)("p",null,"Over the last year and a half, we have established a centralized configuration system for more than 50 projects using Detox at Wix. While it never seemed to be a cakewalk, the entire experience of troubleshooting over a hundred issues across the organization did not leave us unchanged."),(0,a.yg)("p",null,"We see numerous things to improve in Detox, but most of them boil down to the same thing \u2013 ",(0,a.yg)("strong",{parentName:"p"},"scaling"),'. Surprisingly, "scaling" makes an excellent umbrella term for nearly every challenge we\'ve been encountering lately:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of users")," requires us to improve the onboarding and troubleshooting experience;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of projects")," forces us to centralize scattered configs into flexible organization presets;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of tests")," prompts us to optimize the codebase and incline it towards cloud and remote execution.")),(0,a.yg)("p",null,"Our core team has been facing challenges of limited human resource constraints and growing scaling needs for a long time. In many ways, that has shaped a specific mindset within the team. We evaluate every discussed feature by asking a simple question: ",(0,a.yg)("em",{parentName:"p"},"will it save other people and us time to focus on more important things?")," Teaching a man to fish is better than giving fish, so our success at preventing support issues matters more than our success at solving them ourselves."),(0,a.yg)("p",null,"That's why we'll be making subsequent efforts in these three areas, hoping to get back to you soon with even more exciting updates."),(0,a.yg)("p",null,"Enjoy your drive with Detox 20!"),(0,a.yg)("p",null,"Cheers! \ud83d\udc4b"),(0,a.yg)("div",{className:"footnotes"},(0,a.yg)("hr",{parentName:"div"}),(0,a.yg)("ol",{parentName:"div"},(0,a.yg)("li",{parentName:"ol",id:"fn-1-2b8cfe"},"The mentioned threshold is not a hard limit, but rather a point where the return value of scaling up the number of devices starts dramatically diminishing in our case \u2013 not only the tests themselves, but installing NPM dependencies and building the projects also takes time.",(0,a.yg)("a",{parentName:"li",href:"#fnref-1-2b8cfe",className:"footnote-backref"},"\u21a9")))))}d.isMDXComponent=!0},72716:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-logger-options-be0263e8b33e617b2a4ef55861d45e4a.png"},27709:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-perfetto-example-171220190ff552d655a9a0712e2c04ed.png"}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[6941],{15680:(e,t,n)=>{n.d(t,{xA:()=>u,yg:()=>c});var o=n(96540);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=o.createContext({}),p=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},u=function(e){var t=p(e.components);return o.createElement(l.Provider,{value:t},e.children)},g="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},m=o.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),g=p(n),m=a,c=g["".concat(l,".").concat(m)]||g[m]||d[m]||i;return n?o.createElement(c,r(r({ref:t},u),{},{components:n})):o.createElement(c,r({ref:t},u))}));function c(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,r=new Array(i);r[0]=m;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[g]="string"==typeof e?e:a,r[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var o=n(58168),a=(n(96540),n(15680));const i={authors:["noomorph"],tags:["major-release","genymotion"]},r="Detox 20 is out",s={permalink:"/Detox/blog/2022/11/10/detox-20-is-out",editUrl:"https://github.com/wix/Detox/edit/master/website/blog/2022-11-10-detox-20-is-out.md",source:"@site/blog/2022-11-10-detox-20-is-out.md",title:"Detox 20 is out",description:'Today we\'re proud to announce the new major release, Detox 20 (codename "Ash\xe1n"), which brings:',date:"2022-11-10T00:00:00.000Z",formattedDate:"November 10, 2022",tags:[{label:"major-release",permalink:"/Detox/blog/tags/major-release"},{label:"genymotion",permalink:"/Detox/blog/tags/genymotion"}],readingTime:9.705,hasTruncateMarker:!1,authors:[{name:"Yaroslav Serhieiev",title:"Detox Core Contributor",url:"https://github.com/noomorph",imageURL:"https://github.com/noomorph.png",key:"noomorph"}],frontMatter:{authors:["noomorph"],tags:["major-release","genymotion"]},prevItem:{title:"Introducing Detox Copilot",permalink:"/Detox/blog/2024/10/09/detox-copilot-is-out"}},l={authorsImageUrls:[void 0]},p=[{value:"Genymotion SaaS",id:"genymotion-saas",level:2},{value:"Integration with test runners",id:"integration-with-test-runners",level:2},{value:"Configurable logger",id:"configurable-logger",level:2},{value:"Minor features",id:"minor-features",level:2},{value:"Headless iOS",id:"headless-ios",level:3},{value:"Reverse ports",id:"reverse-ports",level:3},{value:"Read-only emulators by default",id:"read-only-emulators-by-default",level:3},{value:"Reset lock file",id:"reset-lock-file",level:3},{value:"Deprecations",id:"deprecations",level:2},{value:"Afterword",id:"afterword",level:2}],u={toc:p},g="wrapper";function d(e){let{components:t,...i}=e;return(0,a.yg)(g,(0,o.A)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,a.yg)("p",null,"Today we're proud to announce the new major release, ",(0,a.yg)("strong",{parentName:"p"},"Detox 20"),' (codename "Ash\xe1n"), which brings:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"official support for Genymotion SaaS"),(0,a.yg)("li",{parentName:"ul"},"improved integration with test runners"),(0,a.yg)("li",{parentName:"ul"},"configurable logging subsystem"),(0,a.yg)("li",{parentName:"ul"},"headless mode for iOS via configs and CLI"),(0,a.yg)("li",{parentName:"ul"},"reversing TCP ports via Android app configs"),(0,a.yg)("li",{parentName:"ul"},"and more optimizations to land in the next minor versions.")),(0,a.yg)("h2",{id:"genymotion-saas"},"Genymotion SaaS"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/genymotion-saas"},(0,a.yg)("inlineCode",{parentName:"a"},"Using Genymotion SaaS")),"."),(0,a.yg)("p",null,"Two years ago we ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/2446"},"added elementary support")," for cloud-based Android emulators provided by ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," and started a beta testing phase across mobile projects at Wix."),(0,a.yg)("p",null,"Previously our mobile infrastructure engineers had been maintaining Android virtual devices on CI build agents on their own, so switching to cloud devices cleared up their time for more productive tasks. Another improvement was particularly noticeable for teams with a vast number of tests. We could reduce the duration of their CI pipelines almost by half after they scaled up from 2 parallel devices to 6",(0,a.yg)("sup",{parentName:"p",id:"fnref-1-2b8cfe"},(0,a.yg)("a",{parentName:"sup",href:"#fn-1-2b8cfe",className:"footnote-ref"},"1")),"."),(0,a.yg)("p",null,"This positive impact encouraged us to adopt ",(0,a.yg)("a",{parentName:"p",href:"https://cloud.geny.io"},"Genymotion SaaS")," for CI as quickly as possible, ignoring some unresolved issues in the initial pull request. For the most part, those were minor problems in global lifecycle management. Yet that made us feel uncertain about releasing it as-is, so we decided to take time and gain more production experience before taking any direction."),(0,a.yg)("p",null,"The further experience was surprisingly smooth and rarely presented issues, spare for a few minor ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3573"},"glitches")," in advanced scenarios. Admittedly, revamping the ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Detox lifecycle")," took us longer than expected, which is all the more reason for us to celebrate today."),(0,a.yg)("p",null,"We're looking forward to providing our users with more opportunities for testing in the cloud, and this step is only the first of many to come. We hope you'll utilize this new feature to your delight."),(0,a.yg)("h2",{id:"integration-with-test-runners"},"Integration with test runners"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Test runner")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},(0,a.yg)("inlineCode",{parentName:"a"},"Internals API")),", ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/issues/3193"},(0,a.yg)("inlineCode",{parentName:"a"},"Dropping Mocha support")),"."),(0,a.yg)("p",null,"It took about a few months of work to formalize the contract between Detox and a test runner. While there's still a lot of place for improvement, the new Detox release refines their interaction and lays the groundwork for third-party integrations."),(0,a.yg)("p",null,(0,a.yg)("a",{parentName:"p",href:"https://mochajs.org"},"Mocha")," was our first supported test runner, but unfortunately, it could not keep up with our scaling requirements as the number of end-to-end tests grew. ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/mochajs/mocha/releases/tag/v8.0.0"},"By the time")," it acquired the ability to run tests in parallel, we already had ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/609"},"to place bets")," on another horse, and that was ",(0,a.yg)("a",{parentName:"p",href:"https://jestjs.io"},"Jest"),"."),(0,a.yg)("p",null,"We attempted to keep compatibility with both Jest and Mocha, but the farther we went, the more obvious it was that we couldn't have it both ways. As it turned out, Jest wasn't easy to get along with \u2013 our first integration with it was too simplistic. Over a couple of years of use in production, we kept discovering various issues that forced us to rewrite our \"glue\" code from scratch twice, and this isn't over yet. All combined didn't leave much time and energy for tinkering with Mocha anymore."),(0,a.yg)("p",null,"In this release, we discontinued Mocha support to focus on the attunement of Jest with the new runner-independent ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/testRunner"},"test runner config")," and ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/internals"},"Internals API"),". If there's enough demand, now it is up to the open-source community to build a new integration between Detox and Mocha."),(0,a.yg)("h2",{id:"configurable-logger"},"Configurable logger"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Highlights"),": ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Config file > Logger")),", ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},(0,a.yg)("inlineCode",{parentName:"a"},"Logger API")),"."),(0,a.yg)("p",null,"The rigidity of the logging subsystem has always been showing itself ",(0,a.yg)("a",{parentName:"p",href:"https://github.com/wix/Detox/pull/835"},"since its very creation")," in the summer of 2019.\nDue to time constraints and existing tech debts, it was impossible to do it right the first time, so we lived about three years with a proof-of-concept rather than a full-fledged feature."),(0,a.yg)("p",null,"The inconveniences weren't fatal but quite noticeable, nevertheless. Here are a few syndromes you could have spotted if you have ever used Detox timeline and log artifacts, especially when running tests in parallel:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"an uncanny file array: ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7505.log.json"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"detox_pid_7506.log"),";"),(0,a.yg)("li",{parentName:"ul"},"a relatively shallow ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json"),": test suites, test functions, and some user-defined segments.")),(0,a.yg)("p",null,"The good news is that the new Detox release condenses all those numerous logs into two files:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"the plain, human-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.log"),";"),(0,a.yg)("li",{parentName:"ul"},"the raw, machine-readable ",(0,a.yg)("inlineCode",{parentName:"li"},"detox.trace.json")," for ",(0,a.yg)("inlineCode",{parentName:"li"},"chrome://trace"),", ",(0,a.yg)("a",{parentName:"li",href:"https://ui.perfetto.dev/"},"Perfetto")," and other utilities.")),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"A screenshot of timeline view generated by Perfetto",src:n(27709).A,width:"1600",height:"1000"})),(0,a.yg)("p",null,"With the help of the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/api/logger"},"Logger API"),", you can add custom duration events to the timeline, too, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"await detox.log.trace.complete('Login', async () => {\n await element(by.id('email')).typeText('john@example.com');\n await element(by.id('password')).typeText('123456');\n\n detox.log.info('Trying to log in...');\n await element(by.id('submit')).tap();\n});\n")),(0,a.yg)("p",null,"Besides, it is possible now to customize the console output of Detox via the new ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/logger"},"logger config"),", e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js",metastring:'title="detox.config.js"',title:'"detox.config.js"'},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n logger: {\n options: {\n showDate: false,\n showLoggerName: false,\n showPid: false,\n prefixers: {\n ph: null,\n },\n },\n },\n};\n")),(0,a.yg)("p",null,"In the example above, we minimize all the metadata around the log messages \u2013 see the screenshot below:"),(0,a.yg)("p",null,(0,a.yg)("img",{alt:"Terser logs after applying the override",src:n(72716).A,width:"1400",height:"399"})),(0,a.yg)("h2",{id:"minor-features"},"Minor features"),(0,a.yg)("h3",{id:"headless-ios"},"Headless iOS"),(0,a.yg)("p",null,"One of Detox known issues was always booting iOS simulators in a hidden mode. You could see tests running on your local simulator only if you had manually opened the Simulator app beforehand. So, we unified the ",(0,a.yg)("inlineCode",{parentName:"p"},"headless")," property for both iOS and Android, and now both the platforms visibly boot a device unless you configure it otherwise, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/* @type {Detox.DetoxConfig} */\nmodule.exports = {\n devices: {\n iphone: {\n type: 'ios.simulator',\n // highlight-next-line\n headless: process.env.CI ? true : undefined,\n device: {\n type: 'iPhone 14'\n },\n /* ... */\n }\n },\n};\n")),(0,a.yg)("p",null,"or, via CLI:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c ios.sim.release --headless\n")),(0,a.yg)("h3",{id:"reverse-ports"},"Reverse ports"),(0,a.yg)("p",null,"Your apps might try to access some ",(0,a.yg)("inlineCode",{parentName:"p"},"localhost:*")," addresses (e.g., mock servers), but this is a bit more problematic in the case of Android. The Android emulators are separate virtual devices with their own loopback network interface. In such cases, you must set up reverse port forwarding via ",(0,a.yg)("inlineCode",{parentName:"p"},"adb reverse"),"."),(0,a.yg)("p",null,"Local servers are quite a common prerequisite for apps in debug mode \u2013 one could recall React Native bundler on port 8081, Storybook server on 9009, etc. That's why we decided to add an optional config property for Android apps, ",(0,a.yg)("inlineCode",{parentName:"p"},"reversePorts"),":"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-js"},"/** @type {Detox.DetoxConfig} */\nmodule.exports = {\n // ...\n apps: {\n 'android.debug': {\n type: 'android.apk',\n binaryPath: '...',\n reversePorts: [8081, 3000],\n },\n },\n};\n")),(0,a.yg)("p",null,"In other words, this is a convenience API that tells Detox to run ",(0,a.yg)("inlineCode",{parentName:"p"},"device.reverseTcpPort(portNumber)")," after installing the app. It should be helpful for anyone who prefers to keep such things as configs rather than as code."),(0,a.yg)("h3",{id:"read-only-emulators-by-default"},"Read-only emulators by default"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," flag appeared in ",(0,a.yg)("a",{parentName:"p",href:"https://developer.android.com/studio/releases/emulator#concurrent-avd"},"Android emulator 28.0.16"),". Detox promptly adopted it since the read-only mode allowed it to run multiple instances of a single Android virtual device (AVD) concurrently. This feature helped us to implement parallel test execution support for Android back then."),(0,a.yg)("p",null,"Being overcautious, we implemented that partially, only for cases when the user starts multiple concurrent workers. This decision created a moderately annoying UX issue. Imagine you run tests sequentially first, using one worker only. That provides you with a regular AVD instance, i.e., not a read-only one. After that, you switch to multiple workers only to get an error from the Android emulator, complaining about mixing regular and read-only instances."),(0,a.yg)("p",null,"While the fix itself has always been straightforward \u2013 close the running AVD and try again \u2013 this entire overcaution brought more issues than solving them. That's why, from now on, Android emulators will always be starting in ",(0,a.yg)("inlineCode",{parentName:"p"},"-read-only")," mode unless you configure ",(0,a.yg)("inlineCode",{parentName:"p"},"readonly: false")," in your ",(0,a.yg)("a",{parentName:"p",href:"/docs/config/devices"},"device config"),"."),(0,a.yg)("h3",{id:"reset-lock-file"},"Reset lock file"),(0,a.yg)("p",null,"This release adds a small CLI tool, ",(0,a.yg)("a",{parentName:"p",href:"/docs/cli/reset-lock-file"},(0,a.yg)("inlineCode",{parentName:"a"},"detox reset-lock-file")),", to help users with one specific use scenario."),(0,a.yg)("p",null,"Imagine you want to run tests for multiple Detox configurations at once, e.g.:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox test -c iphoneSE2020.release e2e/ui.test.js\ndetox test -c iphone14ProMax.release e2e/ui.test.js\n")),(0,a.yg)("p",null,"The problem is that Detox uses a file-locking mechanism to avoid situations when parallel test workers would take control of the same device. The ",(0,a.yg)("inlineCode",{parentName:"p"},"detox test")," command, upon start, erases that file contents, creating a race condition risk."),(0,a.yg)("p",null,"To eliminate that risk, use a combination of ",(0,a.yg)("inlineCode",{parentName:"p"},"detox reset-lock-file")," and ",(0,a.yg)("inlineCode",{parentName:"p"},"--keepLockFile")," like this:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-bash"},"detox reset-lock-file & \\\ndetox test --keepLockFile -c iphoneSE2020.release e2e/ui.test.js & \\\ndetox test --keepLockFile -c iphone14ProMax.release e2e/ui.test.js & \\\nwait\n")),(0,a.yg)("p",null,"In the future, we plan to minimize using lock files so that you don't have to think about this low-level implementation detail.\nSo, this tool adds some convenience until we provide a next-gen solution."),(0,a.yg)("h2",{id:"deprecations"},"Deprecations"),(0,a.yg)("p",null,"Detox 20 executes many pending deprecations, so make sure to check out our ",(0,a.yg)("a",{parentName:"p",href:"/docs/guide/migration#200"},"Migration Guide")," before upgrading:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Node.js version is ",(0,a.yg)("inlineCode",{parentName:"li"},"14.x"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: minimum supported Jest version is ",(0,a.yg)("inlineCode",{parentName:"li"},"27.2.5"),";"),(0,a.yg)("li",{parentName:"ul"},"JS: Mocha test runner is no longer supported;"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued old adapters for Jest (",(0,a.yg)("inlineCode",{parentName:"li"},"jest-jasmine"),", first generation of ",(0,a.yg)("inlineCode",{parentName:"li"},"jest-circus")," adapter);"),(0,a.yg)("li",{parentName:"ul"},"JS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"{ permanent: true }")," option in ",(0,a.yg)("inlineCode",{parentName:"li"},"device.appLaunchArgs.*")," methods (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3360"},"#3360"),");"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped ",(0,a.yg)("inlineCode",{parentName:"li"},"-w, --workers")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"-o, --runner-config")," args \u2013 see a ",(0,a.yg)("a",{parentName:"li",href:"/docs/guide/migration#updating-command-line-scripts"},"dedicated section")," in the migration guide;"),(0,a.yg)("li",{parentName:"ul"},"CLI: dropped deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"--device-launch-args")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3665"},"#3665"),");"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued kebab-case properties: ",(0,a.yg)("inlineCode",{parentName:"li"},"test-runner"),", ",(0,a.yg)("inlineCode",{parentName:"li"},"runner-config")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3371"},"#3371"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"skipLegacyWorkersInjections")," property (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3286"},"(#3286)"),")"),(0,a.yg)("li",{parentName:"ul"},"Config: deprecated ",(0,a.yg)("inlineCode",{parentName:"li"},"specs")," and ",(0,a.yg)("inlineCode",{parentName:"li"},"runnerConfig")," properties"),(0,a.yg)("li",{parentName:"ul"},"Config: changed ",(0,a.yg)("a",{parentName:"li",href:"/docs/config/testRunner"},"semantics")," of ",(0,a.yg)("inlineCode",{parentName:"li"},"testRunner")," property"),(0,a.yg)("li",{parentName:"ul"},"Config: dropped support for all-in-one configurations (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3386"},"#3386"),");"),(0,a.yg)("li",{parentName:"ul"},"Android: remove deprecated native IdlePolicyConfig (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3332/files"},"#3332"),")"),(0,a.yg)("li",{parentName:"ul"},"iOS: discontinued ",(0,a.yg)("inlineCode",{parentName:"li"},"ios.none")," device type \u2013 see the new way to ",(0,a.yg)("a",{parentName:"li",href:"/docs/introduction/debugging#native-application-code"},"debug native code")," (",(0,a.yg)("a",{parentName:"li",href:"https://github.com/wix/Detox/pull/3361"},"#3361"),")")),(0,a.yg)("h2",{id:"afterword"},"Afterword"),(0,a.yg)("p",null,"Over the last year and a half, we have established a centralized configuration system for more than 50 projects using Detox at Wix. While it never seemed to be a cakewalk, the entire experience of troubleshooting over a hundred issues across the organization did not leave us unchanged."),(0,a.yg)("p",null,"We see numerous things to improve in Detox, but most of them boil down to the same thing \u2013 ",(0,a.yg)("strong",{parentName:"p"},"scaling"),'. Surprisingly, "scaling" makes an excellent umbrella term for nearly every challenge we\'ve been encountering lately:'),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of users")," requires us to improve the onboarding and troubleshooting experience;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of projects")," forces us to centralize scattered configs into flexible organization presets;"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("em",{parentName:"li"},"scaling up the number of tests")," prompts us to optimize the codebase and incline it towards cloud and remote execution.")),(0,a.yg)("p",null,"Our core team has been facing challenges of limited human resource constraints and growing scaling needs for a long time. In many ways, that has shaped a specific mindset within the team. We evaluate every discussed feature by asking a simple question: ",(0,a.yg)("em",{parentName:"p"},"will it save other people and us time to focus on more important things?")," Teaching a man to fish is better than giving fish, so our success at preventing support issues matters more than our success at solving them ourselves."),(0,a.yg)("p",null,"That's why we'll be making subsequent efforts in these three areas, hoping to get back to you soon with even more exciting updates."),(0,a.yg)("p",null,"Enjoy your drive with Detox 20!"),(0,a.yg)("p",null,"Cheers! \ud83d\udc4b"),(0,a.yg)("div",{className:"footnotes"},(0,a.yg)("hr",{parentName:"div"}),(0,a.yg)("ol",{parentName:"div"},(0,a.yg)("li",{parentName:"ol",id:"fn-1-2b8cfe"},"The mentioned threshold is not a hard limit, but rather a point where the return value of scaling up the number of devices starts dramatically diminishing in our case \u2013 not only the tests themselves, but installing NPM dependencies and building the projects also takes time.",(0,a.yg)("a",{parentName:"li",href:"#fnref-1-2b8cfe",className:"footnote-backref"},"\u21a9")))))}d.isMDXComponent=!0},72716:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-logger-options-be0263e8b33e617b2a4ef55861d45e4a.png"},27709:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/v20-perfetto-example-171220190ff552d655a9a0712e2c04ed.png"}}]); \ No newline at end of file diff --git a/assets/js/main.2857f36d.js b/assets/js/main.2857f36d.js deleted file mode 100644 index 869977e671..0000000000 --- a/assets/js/main.2857f36d.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! For license information please see main.2857f36d.js.LICENSE.txt */ -(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[8792],{55600:(e,t,n)=>{"use strict";n.d(t,{Bc:()=>g,E8:()=>Un,a1:()=>zn});var r=n(96540);n(40961);function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function l(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,i=[],a=!0,c=!1;try{for(n=n.call(e);!(a=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);a=!0);}catch(e){c=!0,o=e}finally{try{a||null==n.return||n.return()}finally{if(c)throw o}}return i}}(e,t)||d(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(e){return function(e){if(Array.isArray(e))return f(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||d(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function d(e,t){if(e){if("string"==typeof e)return f(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?f(e,t):void 0}}function f(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function N(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function j(e){for(var t=1;t=3||2===n&&r>=4||1===n&&r>=10);function i(t,n,r){if(o&&void 0!==r){var i=r[0].__autocomplete_algoliaCredentials,a={"X-Algolia-Application-Id":i.appId,"X-Algolia-API-Key":i.apiKey};e.apply(void 0,[t].concat(P(n),[{headers:a}]))}else e.apply(void 0,[t].concat(P(n)))}return{init:function(t,n){e("init",{appId:t,apiKey:n})},setUserToken:function(t){e("setUserToken",t)},clickedObjectIDsAfterSearch:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("clickedObjectIDsAfterSearch",M(t),t[0].items)},clickedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("clickedObjectIDs",M(t),t[0].items)},clickedFilters:function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&e.apply(void 0,["clickedFilters"].concat(n))},convertedObjectIDsAfterSearch:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("convertedObjectIDsAfterSearch",M(t),t[0].items)},convertedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&i("convertedObjectIDs",M(t),t[0].items)},convertedFilters:function(){for(var t=arguments.length,n=new Array(t),r=0;r0&&e.apply(void 0,["convertedFilters"].concat(n))},viewedObjectIDs:function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&t.reduce((function(e,t){var n=t.items,r=R(t,C);return[].concat(P(e),P(function(e){for(var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:20,n=[],r=0;r0&&e.apply(void 0,["viewedFilters"].concat(n))}}}function F(e){var t=e.items.reduce((function(e,t){var n;return e[t.__autocomplete_indexName]=(null!==(n=e[t.__autocomplete_indexName])&&void 0!==n?n:[]).concat(t),e}),{});return Object.keys(t).map((function(e){return{index:e,items:t[e],algoliaSource:["autocomplete"]}}))}function z(e){return e.objectID&&e.__autocomplete_indexName&&e.__autocomplete_queryID}function U(e){return U="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},U(e)}function $(e){return function(e){if(Array.isArray(e))return q(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(e){if("string"==typeof e)return q(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?q(e,t):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function q(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&K({onItemsChange:r,items:n,insights:c,state:t}))}}),0);return{name:"aa.algoliaInsightsPlugin",subscribe:function(e){var t=e.setContext,n=e.onSelect,r=e.onActive;a("addAlgoliaAgent","insights-plugin"),t({algoliaInsightsPlugin:{__algoliaSearchParameters:{clickAnalytics:!0},insights:c}}),n((function(e){var t=e.item,n=e.state,r=e.event;z(t)&&o({state:n,event:r,insights:c,item:t,insightsEvents:[V({eventName:"Item Selected"},D({item:t,items:s.current}))]})})),r((function(e){var t=e.item,n=e.state,r=e.event;z(t)&&i({state:n,event:r,insights:c,item:t,insightsEvents:[V({eventName:"Item Active"},D({item:t,items:s.current}))]})}))},onStateChange:function(e){var t=e.state;l({state:t})},__autocomplete_pluginOptions:e}}function Q(e,t){var n=t;return{then:function(t,r){return Q(e.then(X(t,n,e),X(r,n,e)),n)},catch:function(t){return Q(e.catch(X(t,n,e)),n)},finally:function(t){return t&&n.onCancelList.push(t),Q(e.finally(X(t&&function(){return n.onCancelList=[],t()},n,e)),n)},cancel:function(){n.isCanceled=!0;var e=n.onCancelList;n.onCancelList=[],e.forEach((function(e){e()}))},isCanceled:function(){return!0===n.isCanceled}}}function Z(e){return Q(e,{isCanceled:!1,onCancelList:[]})}function X(e,t,n){return e?function(n){return t.isCanceled?n:e(n)}:n}function J(e,t,n,r){if(!n)return null;if(e<0&&(null===t||null!==r&&0===t))return n+e;var o=(null===t?-1:t)+e;return o<=-1||o>=n?null===r?null:0:o}function ee(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function te(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:"autocomplete-".concat(x++),plugins:o,initialState:ge({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(function(e){return function(e){if(Array.isArray(e))return he(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(e){if("string"==typeof e)return he(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?he(e,t):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return function(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t={getItemInputValue:function(e){return e.state.query},getItemUrl:function(){},onSelect:function(e){(0,e.setIsOpen)(!1)},onActive:E,onResolve:E};Object.keys(t).forEach((function(e){t[e].__default=!0}));var r=te(te({},t),e);return Promise.resolve(r)})))}))}(e,n)}))).then((function(e){return y(e)})).then((function(e){return e.map((function(e){return ge(ge({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))},onResolve:function(n){e.onResolve(n),t.forEach((function(e){var t;return null===(t=e.onResolve)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:ge({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}function ye(e){return ye="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},ye(e)}function xe(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function we(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,Ie);Fe&&o.environment.clearTimeout(Fe);var l=s.setCollections,u=s.setIsOpen,d=s.setQuery,f=s.setActiveItemId,p=s.setStatus;if(d(i),f(o.defaultActiveItemId),!i&&!1===o.openOnFocus){var h,m=c.getState().collections.map((function(e){return Ne(Ne({},e),{},{items:[]})}));p("idle"),l(m),u(null!==(h=r.isOpen)&&void 0!==h?h:o.shouldPanelOpen({state:c.getState()}));var g=Z(ze(m).then((function(){return Promise.resolve()})));return c.pendingRequests.add(g)}p("loading"),Fe=o.environment.setTimeout((function(){p("stalled")}),o.stallThreshold);var b=Z(ze(o.getSources(Ne({query:i,refresh:a,state:c.getState()},s)).then((function(e){return Promise.all(e.map((function(e){return Promise.resolve(e.getItems(Ne({query:i,refresh:a,state:c.getState()},s))).then((function(t){return function(e,t,n){if(o=e,Boolean(null==o?void 0:o.execute)){var r="algolia"===e.requesterId?Object.assign.apply(Object,[{}].concat(Ae(Object.keys(n.context).map((function(e){var t;return null===(t=n.context[e])||void 0===t?void 0:t.__algoliaSearchParameters}))))):{};return ke(ke({},e),{},{requests:e.queries.map((function(n){return{query:"algolia"===e.requesterId?ke(ke({},n),{},{params:ke(ke({},r),n.params)}):n,sourceId:t,transformResponse:e.transformResponse}}))})}var o;return{items:e,sourceId:t}}(t,e.sourceId,c.getState())}))}))).then(Te).then((function(t){return function(e,t,n){return t.map((function(t){var r,o=e.filter((function(e){return e.sourceId===t.sourceId})),i=o.map((function(e){return e.items})),a=o[0].transformResponse,c=a?a({results:r=i,hits:r.map((function(e){return e.hits})).filter(Boolean),facetHits:r.map((function(e){var t;return null===(t=e.facetHits)||void 0===t?void 0:t.map((function(e){return{label:e.value,count:e.count,_highlightResult:{label:{value:e.highlighted}}}}))})).filter(Boolean)}):i;return t.onResolve({source:t,results:i,items:c,state:n.getState()}),c.every(Boolean),'The `getItems` function from source "'.concat(t.sourceId,'" must return an array of items but returned ').concat(JSON.stringify(void 0),".\n\nDid you forget to return items?\n\nSee: https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/sources/#param-getitems"),{source:t,items:c}}))}(t,e,c)})).then((function(e){return function(e){var t=e.props,n=e.state,r=e.collections.reduce((function(e,t){return we(we({},e),{},Se({},t.source.sourceId,we(we({},t.source),{},{getItems:function(){return y(t.items)}})))}),{}),o=t.plugins.reduce((function(e,t){return t.reshape?t.reshape(e):e}),{sourcesBySourceId:r,state:n}).sourcesBySourceId;return y(t.reshape({sourcesBySourceId:o,sources:Object.values(o),state:n})).filter(Boolean).map((function(e){return{source:e,items:e.getItems()}}))}({collections:e,props:o,state:c.getState()})}))})))).then((function(e){var n;p("idle"),l(e);var d=o.shouldPanelOpen({state:c.getState()});u(null!==(n=r.isOpen)&&void 0!==n?n:o.openOnFocus&&!i&&d||d);var f=oe(c.getState());if(null!==c.getState().activeItemId&&f){var h=f.item,m=f.itemInputValue,g=f.itemUrl,b=f.source;b.onActive(Ne({event:t,item:h,itemInputValue:m,itemUrl:g,refresh:a,source:b,state:c.getState()},s))}})).finally((function(){p("idle"),Fe&&o.environment.clearTimeout(Fe)}));return c.pendingRequests.add(b)}function $e(e){return $e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},$e(e)}var qe=["event","props","refresh","store"];function He(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ve(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function it(e){var t=e.props,n=e.refresh,r=e.store,o=ot(e,Ke),i=function(e,t){return void 0!==t?"".concat(e,"-").concat(t):e};return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;function a(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return(n=t)===(r=e.target)||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())}return nt({onTouchStart:a,onMouseDown:a,onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},ot(e,Ye))},getRootProps:function(e){return nt({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){return e.inputElement,nt({action:"",noValidate:!0,role:"search",onSubmit:function(i){var a;i.preventDefault(),t.onSubmit(nt({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(a=e.inputElement)||void 0===a||a.blur()},onReset:function(i){var a;i.preventDefault(),t.onReset(nt({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(a=e.inputElement)||void 0===a||a.focus()}},ot(e,Qe))},getLabelProps:function(e){var n=e||{},r=n.sourceIndex,o=ot(n,Xe);return nt({htmlFor:"".concat(i(t.id,r),"-input"),id:"".concat(i(t.id,r),"-label")},o)},getInputProps:function(e){var i;function a(e){(t.openOnFocus||Boolean(r.getState().query))&&Ue(nt({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var c=e||{},s=(c.inputElement,c.maxLength),l=void 0===s?512:s,u=ot(c,Ze),d=oe(r.getState()),f=function(e){return Boolean(e&&e.match(ie))}((null===(i=t.environment.navigator)||void 0===i?void 0:i.userAgent)||""),p=null!=d&&d.itemUrl&&!f?"go":"search";return nt({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:p,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:l,type:"search",onChange:function(e){Ue(nt({event:e,props:t,query:e.currentTarget.value.slice(0,l),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,qe);if("ArrowUp"===t.key||"ArrowDown"===t.key){var a=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},c=function(){var e=oe(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,a=e.itemInputValue,c=e.itemUrl,s=e.source;s.onActive(Ve({event:t,item:n,itemInputValue:a,itemUrl:c,refresh:r,source:s,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?Ue(Ve({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),c(),setTimeout(a,0)})):(o.dispatch(t.key,{}),c(),a())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(n.debug||o.pendingRequests.cancelAll());t.preventDefault();var s=oe(o.getState()),l=s.item,u=s.itemInputValue,d=s.itemUrl,f=s.source;if(t.metaKey||t.ctrlKey)void 0!==d&&(f.onSelect(Ve({event:t,item:l,itemInputValue:u,itemUrl:d,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:d,item:l,state:o.getState()}));else if(t.shiftKey)void 0!==d&&(f.onSelect(Ve({event:t,item:l,itemInputValue:u,itemUrl:d,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:d,item:l,state:o.getState()}));else if(t.altKey);else{if(void 0!==d)return f.onSelect(Ve({event:t,item:l,itemInputValue:u,itemUrl:d,refresh:r,source:f,state:o.getState()},i)),void n.navigator.navigate({itemUrl:d,item:l,state:o.getState()});Ue(Ve({event:t,nextState:{isOpen:!1},props:n,query:u,refresh:r,store:o},i)).then((function(){f.onSelect(Ve({event:t,item:l,itemInputValue:u,itemUrl:d,refresh:r,source:f,state:o.getState()},i))}))}}}(nt({event:e,props:t,refresh:n,store:r},o))},onFocus:a,onBlur:E,onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||a(n)}},u)},getPanelProps:function(e){return nt({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){var n=e||{},r=n.sourceIndex,o=ot(n,Je);return nt({role:"listbox","aria-labelledby":"".concat(i(t.id,r),"-label"),id:"".concat(i(t.id,r),"-list")},o)},getItemProps:function(e){var a=e.item,c=e.source,s=e.sourceIndex,l=ot(e,et);return nt({id:"".concat(i(t.id,s),"-item-").concat(a.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===a.__autocomplete_id,onMouseMove:function(e){if(a.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",a.__autocomplete_id);var t=oe(r.getState());if(null!==r.getState().activeItemId&&t){var i=t.item,c=t.itemInputValue,s=t.itemUrl,l=t.source;l.onActive(nt({event:e,item:i,itemInputValue:c,itemUrl:s,refresh:n,source:l,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var i=c.getItemInputValue({item:a,state:r.getState()}),s=c.getItemUrl({item:a,state:r.getState()});(s?Promise.resolve():Ue(nt({event:e,nextState:{isOpen:!1},props:t,query:i,refresh:n,store:r},o))).then((function(){c.onSelect(nt({event:e,item:a,itemInputValue:i,itemUrl:s,refresh:n,source:c,state:r.getState()},o))}))}},l)}}}function at(e){return at="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},at(e)}function ct(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function st(e){for(var t=1;t0&&r.createElement("div",{className:"DocSearch-NoResults-Prefill-List"},r.createElement("p",{className:"DocSearch-Help"},l,":"),r.createElement("ul",null,m.slice(0,3).reduce((function(e,t){return[].concat(u(e),[r.createElement("li",{key:t},r.createElement("button",{className:"DocSearch-Prefill",key:t,type:"button",onClick:function(){o.setQuery(t.toLowerCase()+" "),o.refresh(),o.inputRef.current.focus()}},t))])}),[]))),o.getMissingResultsUrl&&r.createElement("p",{className:"DocSearch-Help"},"".concat(f," "),r.createElement("a",{href:o.getMissingResultsUrl({query:o.state.query}),target:"_blank",rel:"noopener noreferrer"},h)))}var zt=["hit","attribute","tagName"];function Ut(e,t){return t.split(".").reduce((function(e,t){return null!=e&&e[t]?e[t]:null}),e)}function $t(e){var t=e.hit,n=e.attribute,o=e.tagName,a=void 0===o?"span":o,c=s(e,zt);return(0,r.createElement)(a,i(i({},c),{},{dangerouslySetInnerHTML:{__html:Ut(t,"_snippetResult.".concat(n,".value"))||Ut(t,n)}}))}function qt(e){return e.collection&&0!==e.collection.items.length?r.createElement("section",{className:"DocSearch-Hits"},r.createElement("div",{className:"DocSearch-Hit-source"},e.title),r.createElement("ul",e.getListProps(),e.collection.items.map((function(t,n){return r.createElement(Ht,c({key:[e.title,t.objectID].join(":"),item:t,index:n},e))})))):null}function Ht(e){var t=e.item,n=e.index,o=e.renderIcon,i=e.renderAction,a=e.getItemProps,s=e.onItemClick,u=e.collection,d=e.hitComponent,f=l(r.useState(!1),2),p=f[0],h=f[1],m=l(r.useState(!1),2),g=m[0],b=m[1],v=r.useRef(null),y=d;return r.createElement("li",c({className:["DocSearch-Hit",t.__docsearch_parent&&"DocSearch-Hit--Child",p&&"DocSearch-Hit--deleting",g&&"DocSearch-Hit--favoriting"].filter(Boolean).join(" "),onTransitionEnd:function(){v.current&&v.current()}},a({item:t,source:u.source,onClick:function(e){s(t,e)}})),r.createElement(y,{hit:t},r.createElement("div",{className:"DocSearch-Hit-Container"},o({item:t,index:n}),t.hierarchy[t.type]&&"lvl1"===t.type&&r.createElement("div",{className:"DocSearch-Hit-content-wrapper"},r.createElement($t,{className:"DocSearch-Hit-title",hit:t,attribute:"hierarchy.lvl1"}),t.content&&r.createElement($t,{className:"DocSearch-Hit-path",hit:t,attribute:"content"})),t.hierarchy[t.type]&&("lvl2"===t.type||"lvl3"===t.type||"lvl4"===t.type||"lvl5"===t.type||"lvl6"===t.type)&&r.createElement("div",{className:"DocSearch-Hit-content-wrapper"},r.createElement($t,{className:"DocSearch-Hit-title",hit:t,attribute:"hierarchy.".concat(t.type)}),r.createElement($t,{className:"DocSearch-Hit-path",hit:t,attribute:"hierarchy.lvl1"})),"content"===t.type&&r.createElement("div",{className:"DocSearch-Hit-content-wrapper"},r.createElement($t,{className:"DocSearch-Hit-title",hit:t,attribute:"content"}),r.createElement($t,{className:"DocSearch-Hit-path",hit:t,attribute:"hierarchy.lvl1"})),i({item:t,runDeleteTransition:function(e){h(!0),v.current=e},runFavoriteTransition:function(e){b(!0),v.current=e}}))))}function Vt(e,t,n){return e.reduce((function(e,r){var o=t(r);return e.hasOwnProperty(o)||(e[o]=[]),e[o].length<(n||5)&&e[o].push(r),e}),{})}function Wt(e){return e}function Gt(e){return 1===e.button||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey}function Kt(){}var Yt=/(|<\/mark>)/g,Qt=RegExp(Yt.source);function Zt(e){var t,n,r=e;if(!r.__docsearch_parent&&!e._highlightResult)return e.hierarchy.lvl0;var o=r.__docsearch_parent?null===(t=r.__docsearch_parent)||void 0===t||null===(t=t._highlightResult)||void 0===t||null===(t=t.hierarchy)||void 0===t?void 0:t.lvl0:null===(n=e._highlightResult)||void 0===n||null===(n=n.hierarchy)||void 0===n?void 0:n.lvl0;return o?o.value&&Qt.test(o.value)?o.value.replace(Yt,""):o.value:e.hierarchy.lvl0}function Xt(e){return r.createElement("div",{className:"DocSearch-Dropdown-Container"},e.state.collections.map((function(t){if(0===t.items.length)return null;var n=Zt(t.items[0]);return r.createElement(qt,c({},e,{key:t.source.sourceId,title:n,collection:t,renderIcon:function(e){var n,o=e.item,i=e.index;return r.createElement(r.Fragment,null,o.__docsearch_parent&&r.createElement("svg",{className:"DocSearch-Hit-Tree",viewBox:"0 0 24 54"},r.createElement("g",{stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"},o.__docsearch_parent!==(null===(n=t.items[i+1])||void 0===n?void 0:n.__docsearch_parent)?r.createElement("path",{d:"M8 6v21M20 27H8.3"}):r.createElement("path",{d:"M8 6v42M20 27H8.3"}))),r.createElement("div",{className:"DocSearch-Hit-icon"},r.createElement(Pt,{type:o.type})))},renderAction:function(){return r.createElement("div",{className:"DocSearch-Hit-action"},r.createElement(Ct,null))}}))})),e.resultsFooterComponent&&r.createElement("section",{className:"DocSearch-HitsFooter"},r.createElement(e.resultsFooterComponent,{state:e.state})))}var Jt=["translations"];function en(e){var t=e.translations,n=void 0===t?{}:t,o=s(e,Jt),i=n.recentSearchesTitle,a=void 0===i?"Recent":i,l=n.noRecentSearchesText,u=void 0===l?"No recent searches":l,d=n.saveRecentSearchButtonTitle,f=void 0===d?"Save this search":d,p=n.removeRecentSearchButtonTitle,h=void 0===p?"Remove this search from history":p,m=n.favoriteSearchesTitle,g=void 0===m?"Favorite":m,b=n.removeFavoriteSearchButtonTitle,v=void 0===b?"Remove this search from favorites":b;return"idle"===o.state.status&&!1===o.hasCollections?o.disableUserPersonalization?null:r.createElement("div",{className:"DocSearch-StartScreen"},r.createElement("p",{className:"DocSearch-Help"},u)):!1===o.hasCollections?null:r.createElement("div",{className:"DocSearch-Dropdown-Container"},r.createElement(qt,c({},o,{title:a,collection:o.state.collections[0],renderIcon:function(){return r.createElement("div",{className:"DocSearch-Hit-icon"},r.createElement(At,null))},renderAction:function(e){var t=e.item,n=e.runFavoriteTransition,i=e.runDeleteTransition;return r.createElement(r.Fragment,null,r.createElement("div",{className:"DocSearch-Hit-action"},r.createElement("button",{className:"DocSearch-Hit-action-button",title:f,type:"submit",onClick:function(e){e.preventDefault(),e.stopPropagation(),n((function(){o.favoriteSearches.add(t),o.recentSearches.remove(t),o.refresh()}))}},r.createElement(Nt,null))),r.createElement("div",{className:"DocSearch-Hit-action"},r.createElement("button",{className:"DocSearch-Hit-action-button",title:h,type:"submit",onClick:function(e){e.preventDefault(),e.stopPropagation(),i((function(){o.recentSearches.remove(t),o.refresh()}))}},r.createElement(Ot,null))))}})),r.createElement(qt,c({},o,{title:g,collection:o.state.collections[1],renderIcon:function(){return r.createElement("div",{className:"DocSearch-Hit-icon"},r.createElement(Nt,null))},renderAction:function(e){var t=e.item,n=e.runDeleteTransition;return r.createElement("div",{className:"DocSearch-Hit-action"},r.createElement("button",{className:"DocSearch-Hit-action-button",title:v,type:"submit",onClick:function(e){e.preventDefault(),e.stopPropagation(),n((function(){o.favoriteSearches.remove(t),o.refresh()}))}},r.createElement(Ot,null)))}})))}var tn=["translations"],nn=r.memo((function(e){var t=e.translations,n=void 0===t?{}:t,o=s(e,tn);if("error"===o.state.status)return r.createElement(Mt,{translations:null==n?void 0:n.errorScreen});var i=o.state.collections.some((function(e){return e.items.length>0}));return o.state.query?!1===i?r.createElement(Ft,c({},o,{translations:null==n?void 0:n.noResultsScreen})):r.createElement(Xt,o):r.createElement(en,c({},o,{hasCollections:i,translations:null==n?void 0:n.startScreen}))}),(function(e,t){return"loading"===t.state.status||"stalled"===t.state.status})),rn=["translations"];function on(e){var t=e.translations,n=void 0===t?{}:t,o=s(e,rn),i=n.resetButtonTitle,a=void 0===i?"Clear the query":i,l=n.resetButtonAriaLabel,u=void 0===l?"Clear the query":l,d=n.cancelButtonText,f=void 0===d?"Cancel":d,p=n.cancelButtonAriaLabel,m=void 0===p?"Cancel":p,g=n.searchInputLabel,b=void 0===g?"Search":g,v=o.getFormProps({inputElement:o.inputRef.current}).onReset;return r.useEffect((function(){o.autoFocus&&o.inputRef.current&&o.inputRef.current.focus()}),[o.autoFocus,o.inputRef]),r.useEffect((function(){o.isFromSelection&&o.inputRef.current&&o.inputRef.current.select()}),[o.isFromSelection,o.inputRef]),r.createElement(r.Fragment,null,r.createElement("form",{className:"DocSearch-Form",onSubmit:function(e){e.preventDefault()},onReset:v},r.createElement("label",c({className:"DocSearch-MagnifierLabel"},o.getLabelProps()),r.createElement(h,null),r.createElement("span",{className:"DocSearch-VisuallyHiddenForAccessibility"},b)),r.createElement("div",{className:"DocSearch-LoadingIndicator"},r.createElement(Dt,null)),r.createElement("input",c({className:"DocSearch-Input",ref:o.inputRef},o.getInputProps({inputElement:o.inputRef.current,autoFocus:o.autoFocus,maxLength:64}))),r.createElement("button",{type:"reset",title:a,className:"DocSearch-Reset","aria-label":u,hidden:!o.state.query},r.createElement(Ot,null))),r.createElement("button",{className:"DocSearch-Cancel",type:"reset","aria-label":m,onClick:o.onClose},f))}var an=["_highlightResult","_snippetResult"];function cn(e){var t=e.key,n=e.limit,r=void 0===n?5:n,o=function(e){return!1===function(){var e="__TEST_KEY__";try{return localStorage.setItem(e,""),localStorage.removeItem(e),!0}catch(e){return!1}}()?{setItem:function(){},getItem:function(){return[]}}:{setItem:function(t){return window.localStorage.setItem(e,JSON.stringify(t))},getItem:function(){var t=window.localStorage.getItem(e);return t?JSON.parse(t):[]}}}(t),i=o.getItem().slice(0,r);return{add:function(e){var t=e,n=(t._highlightResult,t._snippetResult,s(t,an)),a=i.findIndex((function(e){return e.objectID===n.objectID}));a>-1&&i.splice(a,1),i.unshift(n),i=i.slice(0,r),o.setItem(i)},remove:function(e){i=i.filter((function(t){return t.objectID!==e.objectID})),o.setItem(i)},getAll:function(){return i}}}function sn(e){const t=`algoliasearch-client-js-${e.key}`;let n;const r=()=>(void 0===n&&(n=e.localStorage||window.localStorage),n),o=()=>JSON.parse(r().getItem(t)||"{}"),i=e=>{r().setItem(t,JSON.stringify(e))};return{get:(t,n,r={miss:()=>Promise.resolve()})=>Promise.resolve().then((()=>{(()=>{const t=e.timeToLive?1e3*e.timeToLive:null,n=o(),r=Object.fromEntries(Object.entries(n).filter((([,e])=>void 0!==e.timestamp)));if(i(r),!t)return;const a=Object.fromEntries(Object.entries(r).filter((([,e])=>{const n=(new Date).getTime();return!(e.timestamp+tPromise.all([e?e.value:n(),void 0!==e]))).then((([e,t])=>Promise.all([e,t||r.miss(e)]))).then((([e])=>e)),set:(e,n)=>Promise.resolve().then((()=>{const i=o();return i[JSON.stringify(e)]={timestamp:(new Date).getTime(),value:n},r().setItem(t,JSON.stringify(i)),n})),delete:e=>Promise.resolve().then((()=>{const n=o();delete n[JSON.stringify(e)],r().setItem(t,JSON.stringify(n))})),clear:()=>Promise.resolve().then((()=>{r().removeItem(t)}))}}function ln(e){const t=[...e.caches],n=t.shift();return void 0===n?{get:(e,t,n={miss:()=>Promise.resolve()})=>t().then((e=>Promise.all([e,n.miss(e)]))).then((([e])=>e)),set:(e,t)=>Promise.resolve(t),delete:e=>Promise.resolve(),clear:()=>Promise.resolve()}:{get:(e,r,o={miss:()=>Promise.resolve()})=>n.get(e,r,o).catch((()=>ln({caches:t}).get(e,r,o))),set:(e,r)=>n.set(e,r).catch((()=>ln({caches:t}).set(e,r))),delete:e=>n.delete(e).catch((()=>ln({caches:t}).delete(e))),clear:()=>n.clear().catch((()=>ln({caches:t}).clear()))}}function un(e={serializable:!0}){let t={};return{get(n,r,o={miss:()=>Promise.resolve()}){const i=JSON.stringify(n);if(i in t)return Promise.resolve(e.serializable?JSON.parse(t[i]):t[i]);const a=r(),c=o&&o.miss||(()=>Promise.resolve());return a.then((e=>c(e))).then((()=>a))},set:(n,r)=>(t[JSON.stringify(n)]=e.serializable?JSON.stringify(r):r,Promise.resolve(r)),delete:e=>(delete t[JSON.stringify(e)],Promise.resolve()),clear:()=>(t={},Promise.resolve())}}function dn(e){let t=e.length-1;for(;t>0;t--){const n=Math.floor(Math.random()*(t+1)),r=e[t];e[t]=e[n],e[n]=r}return e}function fn(e,t){return t?(Object.keys(t).forEach((n=>{e[n]=t[n](e)})),e):e}function pn(e,...t){let n=0;return e.replace(/%s/g,(()=>encodeURIComponent(t[n++])))}const hn={WithinQueryParameters:0,WithinHeaders:1};function mn(e,t){const n=e||{},r=n.data||{};return Object.keys(n).forEach((e=>{-1===["timeout","headers","queryParameters","data","cacheable"].indexOf(e)&&(r[e]=n[e])})),{data:Object.entries(r).length>0?r:void 0,timeout:n.timeout||t,headers:n.headers||{},queryParameters:n.queryParameters||{},cacheable:n.cacheable}}const gn={Read:1,Write:2,Any:3};function bn(e,t=1){return{...e,status:t,lastUpdate:Date.now()}}function vn(e){return"string"==typeof e?{protocol:"https",url:e,accept:gn.Any}:{protocol:e.protocol||"https",url:e.url,accept:e.accept||gn.Any}}const yn="GET",xn="POST";function wn(e,t,n,r){const o=[],i=function(e,t){if(e.method===yn||void 0===e.data&&void 0===t.data)return;const n=Array.isArray(e.data)?e.data:{...e.data,...t.data};return JSON.stringify(n)}(n,r),a=function(e,t){const n={...e.headers,...t.headers},r={};return Object.keys(n).forEach((e=>{const t=n[e];r[e.toLowerCase()]=t})),r}(e,r),c=n.method,s=n.method!==yn?{}:{...n.data,...r.data},l={"x-algolia-agent":e.userAgent.value,...e.queryParameters,...s,...r.queryParameters};let u=0;const d=(t,s)=>{const f=t.pop();if(void 0===f)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:kn(o)};const p={data:i,headers:a,method:c,url:_n(f,n.path,l),connectTimeout:s(u,e.timeouts.connect),responseTimeout:s(u,r.timeout)},h=e=>{const n={request:p,response:e,host:f,triesLeft:t.length};return o.push(n),n},m={onSuccess:e=>function(e){try{return JSON.parse(e.content)}catch(t){throw function(e,t){return{name:"DeserializationError",message:e,response:t}}(t.message,e)}}(e),onRetry(n){const r=h(n);return n.isTimedOut&&u++,Promise.all([e.logger.info("Retryable failure",Dn(r)),e.hostsCache.set(f,bn(f,n.isTimedOut?3:2))]).then((()=>d(t,s)))},onFail(e){throw h(e),function({content:e,status:t},n){let r=e;try{r=JSON.parse(e).message}catch(e){}return function(e,t,n){return{name:"ApiError",message:e,status:t,transporterStackTrace:n}}(r,t,n)}(e,kn(o))}};return e.requester.send(p).then((e=>((e,t)=>(e=>{const t=e.status;return e.isTimedOut||(({isTimedOut:e,status:t})=>!e&&!~~t)(e)||2!=~~(t/100)&&4!=~~(t/100)})(e)?t.onRetry(e):(({status:e})=>2==~~(e/100))(e)?t.onSuccess(e):t.onFail(e))(e,m)))};return function(e,t){return Promise.all(t.map((t=>e.get(t,(()=>Promise.resolve(bn(t))))))).then((e=>{const n=e.filter((e=>function(e){return 1===e.status||Date.now()-e.lastUpdate>12e4}(e))),r=e.filter((e=>function(e){return 3===e.status&&Date.now()-e.lastUpdate<=12e4}(e))),o=[...n,...r];return{getTimeout:(e,t)=>(0===r.length&&0===e?1:r.length+3+e)*t,statelessHosts:o.length>0?o.map((e=>vn(e))):t}}))}(e.hostsCache,t).then((e=>d([...e.statelessHosts].reverse(),e.getTimeout)))}function Sn(e){const t={value:`Algolia for JavaScript (${e})`,add(e){const n=`; ${e.segment}${void 0!==e.version?` (${e.version})`:""}`;return-1===t.value.indexOf(n)&&(t.value=`${t.value}${n}`),t}};return t}function _n(e,t,n){const r=En(n);let o=`${e.protocol}://${e.url}/${"/"===t.charAt(0)?t.substr(1):t}`;return r.length&&(o+=`?${r}`),o}function En(e){return Object.keys(e).map((t=>{return pn("%s=%s",t,(n=e[t],"[object Object]"===Object.prototype.toString.call(n)||"[object Array]"===Object.prototype.toString.call(n)?JSON.stringify(e[t]):e[t]));var n})).join("&")}function kn(e){return e.map((e=>Dn(e)))}function Dn(e){const t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...e,request:{...e.request,headers:{...e.request.headers,...t}}}}const An=e=>{const t=e.appId,n=function(e,t,n){const r={"x-algolia-api-key":n,"x-algolia-application-id":t};return{headers:()=>e===hn.WithinHeaders?r:{},queryParameters:()=>e===hn.WithinQueryParameters?r:{}}}(void 0!==e.authMode?e.authMode:hn.WithinHeaders,t,e.apiKey),r=function(e){const{hostsCache:t,logger:n,requester:r,requestsCache:o,responsesCache:i,timeouts:a,userAgent:c,hosts:s,queryParameters:l,headers:u}=e,d={hostsCache:t,logger:n,requester:r,requestsCache:o,responsesCache:i,timeouts:a,userAgent:c,headers:u,queryParameters:l,hosts:s.map((e=>vn(e))),read(e,t){const n=mn(t,d.timeouts.read),r=()=>wn(d,d.hosts.filter((e=>!!(e.accept&gn.Read))),e,n);if(!0!==(void 0!==n.cacheable?n.cacheable:e.cacheable))return r();const o={request:e,mappedRequestOptions:n,transporter:{queryParameters:d.queryParameters,headers:d.headers}};return d.responsesCache.get(o,(()=>d.requestsCache.get(o,(()=>d.requestsCache.set(o,r()).then((e=>Promise.all([d.requestsCache.delete(o),e])),(e=>Promise.all([d.requestsCache.delete(o),Promise.reject(e)]))).then((([e,t])=>t))))),{miss:e=>d.responsesCache.set(o,e)})},write:(e,t)=>wn(d,d.hosts.filter((e=>!!(e.accept&gn.Write))),e,mn(t,d.timeouts.write))};return d}({hosts:[{url:`${t}-dsn.algolia.net`,accept:gn.Read},{url:`${t}.algolia.net`,accept:gn.Write}].concat(dn([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}])),...e,headers:{...n.headers(),"content-type":"application/x-www-form-urlencoded",...e.headers},queryParameters:{...n.queryParameters(),...e.queryParameters}}),o={transporter:r,appId:t,addAlgoliaAgent(e,t){r.userAgent.add({segment:e,version:t})},clearCache:()=>Promise.all([r.requestsCache.clear(),r.responsesCache.clear()]).then((()=>{}))};return fn(o,e.methods)},On=e=>(t,n)=>t.method===yn?e.transporter.read(t,n):e.transporter.write(t,n),Cn=e=>(t,n={})=>fn({transporter:e.transporter,appId:e.appId,indexName:t},n.methods),Tn=e=>(t,n)=>{const r=t.map((e=>({...e,params:En(e.params||{})})));return e.transporter.read({method:xn,path:"1/indexes/*/queries",data:{requests:r},cacheable:!0},n)},Pn=e=>(t,n)=>Promise.all(t.map((t=>{const{facetName:r,facetQuery:o,...i}=t.params;return Cn(e)(t.indexName,{methods:{searchForFacetValues:Nn}}).searchForFacetValues(r,o,{...n,...i})}))),In=e=>(t,n,r)=>e.transporter.read({method:xn,path:pn("1/answers/%s/prediction",e.indexName),data:{query:t,queryLanguages:n},cacheable:!0},r),Rn=e=>(t,n)=>e.transporter.read({method:xn,path:pn("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},n),Nn=e=>(t,n,r)=>e.transporter.read({method:xn,path:pn("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:n},cacheable:!0},r),jn=1,Ln=2,Mn=3;function Bn(e,t,n){const r={appId:e,apiKey:t,timeouts:{connect:1,read:2,write:30},requester:{send:e=>new Promise((t=>{const n=new XMLHttpRequest;n.open(e.method,e.url,!0),Object.keys(e.headers).forEach((t=>n.setRequestHeader(t,e.headers[t])));const r=(e,r)=>setTimeout((()=>{n.abort(),t({status:0,content:r,isTimedOut:!0})}),1e3*e),o=r(e.connectTimeout,"Connection timeout");let i;n.onreadystatechange=()=>{n.readyState>n.OPENED&&void 0===i&&(clearTimeout(o),i=r(e.responseTimeout,"Socket timeout"))},n.onerror=()=>{0===n.status&&(clearTimeout(o),clearTimeout(i),t({content:n.responseText||"Network request failed",status:n.status,isTimedOut:!1}))},n.onload=()=>{clearTimeout(o),clearTimeout(i),t({content:n.responseText,status:n.status,isTimedOut:!1})},n.send(e.data)}))},logger:(o=Mn,{debug:(e,t)=>(jn>=o&&console.debug(e,t),Promise.resolve()),info:(e,t)=>(Ln>=o&&console.info(e,t),Promise.resolve()),error:(e,t)=>(console.error(e,t),Promise.resolve())}),responsesCache:un(),requestsCache:un({serializable:!1}),hostsCache:ln({caches:[sn({key:`4.19.1-${e}`}),un()]}),userAgent:Sn("4.19.1").add({segment:"Browser",version:"lite"}),authMode:hn.WithinQueryParameters};var o;return An({...r,...n,methods:{search:Tn,searchForFacetValues:Pn,multipleQueries:Tn,multipleSearchForFacetValues:Pn,customRequest:On,initIndex:e=>t=>Cn(e)(t,{methods:{search:Rn,searchForFacetValues:Nn,findAnswers:In}})}})}Bn.version="4.19.1";var Fn=["footer","searchBox"];function zn(e){var t=e.appId,n=e.apiKey,o=e.indexName,a=e.placeholder,u=void 0===a?"Search docs":a,d=e.searchParameters,f=e.maxResultsPerGroup,p=e.onClose,h=void 0===p?Kt:p,m=e.transformItems,g=void 0===m?Wt:m,b=e.hitComponent,v=void 0===b?kt:b,y=e.resultsFooterComponent,x=void 0===y?function(){return null}:y,w=e.navigator,S=e.initialScrollY,_=void 0===S?0:S,E=e.transformSearchClient,k=void 0===E?Wt:E,D=e.disableUserPersonalization,A=void 0!==D&&D,O=e.initialQuery,C=void 0===O?"":O,T=e.translations,P=void 0===T?{}:T,I=e.getMissingResultsUrl,R=e.insights,N=void 0!==R&&R,j=P.footer,L=P.searchBox,M=s(P,Fn),B=l(r.useState({query:"",collections:[],completion:null,context:{},isOpen:!1,activeItemId:null,status:"idle"}),2),F=B[0],z=B[1],U=r.useRef(null),$=r.useRef(null),q=r.useRef(null),H=r.useRef(null),V=r.useRef(null),W=r.useRef(10),G=r.useRef("undefined"!=typeof window?window.getSelection().toString().slice(0,64):"").current,K=r.useRef(C||G).current,Y=function(e,t,n){return r.useMemo((function(){var r=Bn(e,t);return r.addAlgoliaAgent("docsearch","3.6.2"),!1===/docsearch.js \(.*\)/.test(r.transporter.userAgent.value)&&r.addAlgoliaAgent("docsearch-react","3.6.2"),n(r)}),[e,t,n])}(t,n,k),Q=r.useRef(cn({key:"__DOCSEARCH_FAVORITE_SEARCHES__".concat(o),limit:10})).current,Z=r.useRef(cn({key:"__DOCSEARCH_RECENT_SEARCHES__".concat(o),limit:0===Q.getAll().length?7:4})).current,X=r.useCallback((function(e){if(!A){var t="content"===e.type?e.__docsearch_parent:e;t&&-1===Q.getAll().findIndex((function(e){return e.objectID===t.objectID}))&&Z.add(t)}}),[Q,Z,A]),J=r.useCallback((function(e){if(F.context.algoliaInsightsPlugin&&e.__autocomplete_id){var t=e,n={eventName:"Item Selected",index:t.__autocomplete_indexName,items:[t],positions:[e.__autocomplete_id],queryID:t.__autocomplete_queryID};F.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(n)}}),[F.context.algoliaInsightsPlugin]),ee=r.useMemo((function(){return wt({id:"docsearch",defaultActiveItemId:0,placeholder:u,openOnFocus:!0,initialState:{query:K,context:{searchSuggestions:[]}},insights:N,navigator:w,onStateChange:function(e){z(e.state)},getSources:function(e){var r=e.query,a=e.state,c=e.setContext,s=e.setStatus;if(!r)return A?[]:[{sourceId:"recentSearches",onSelect:function(e){var t=e.item,n=e.event;X(t),Gt(n)||h()},getItemUrl:function(e){return e.item.url},getItems:function(){return Z.getAll()}},{sourceId:"favoriteSearches",onSelect:function(e){var t=e.item,n=e.event;X(t),Gt(n)||h()},getItemUrl:function(e){return e.item.url},getItems:function(){return Q.getAll()}}];var l=Boolean(N);return Y.search([{query:r,indexName:o,params:i({attributesToRetrieve:["hierarchy.lvl0","hierarchy.lvl1","hierarchy.lvl2","hierarchy.lvl3","hierarchy.lvl4","hierarchy.lvl5","hierarchy.lvl6","content","type","url"],attributesToSnippet:["hierarchy.lvl1:".concat(W.current),"hierarchy.lvl2:".concat(W.current),"hierarchy.lvl3:".concat(W.current),"hierarchy.lvl4:".concat(W.current),"hierarchy.lvl5:".concat(W.current),"hierarchy.lvl6:".concat(W.current),"content:".concat(W.current)],snippetEllipsisText:"\u2026",highlightPreTag:"",highlightPostTag:"",hitsPerPage:20,clickAnalytics:l},d)}]).catch((function(e){throw"RetryError"===e.name&&s("error"),e})).then((function(e){var r=e.results[0],s=r.hits,u=r.nbHits,d=Vt(s,(function(e){return Zt(e)}),f);a.context.searchSuggestions.length0&&(re(),V.current&&V.current.focus())}),[K,re]),r.useEffect((function(){function e(){if($.current){var e=.01*window.innerHeight;$.current.style.setProperty("--docsearch-vh","".concat(e,"px"))}}return e(),window.addEventListener("resize",e),function(){window.removeEventListener("resize",e)}}),[]),r.createElement("div",c({ref:U},ne({"aria-expanded":!0}),{className:["DocSearch","DocSearch-Container","stalled"===F.status&&"DocSearch-Container--Stalled","error"===F.status&&"DocSearch-Container--Errored"].filter(Boolean).join(" "),role:"button",tabIndex:0,onMouseDown:function(e){e.target===e.currentTarget&&h()}}),r.createElement("div",{className:"DocSearch-Modal",ref:$},r.createElement("header",{className:"DocSearch-SearchBar",ref:q},r.createElement(on,c({},ee,{state:F,autoFocus:0===K.length,inputRef:V,isFromSelection:Boolean(K)&&K===G,translations:L,onClose:h}))),r.createElement("div",{className:"DocSearch-Dropdown",ref:H},r.createElement(nn,c({},ee,{indexName:o,state:F,hitComponent:v,resultsFooterComponent:x,disableUserPersonalization:A,recentSearches:Z,favoriteSearches:Q,inputRef:V,translations:M,getMissingResultsUrl:I,onItemClick:function(e,t){J(e),X(e),Gt(t)||h()}}))),r.createElement("footer",{className:"DocSearch-Footer"},r.createElement(Et,{translations:j}))))}function Un(e){var t=e.isOpen,n=e.onOpen,o=e.onClose,i=e.onInput,a=e.searchButtonRef;r.useEffect((function(){function e(e){var r;(27===e.keyCode&&t||"k"===(null===(r=e.key)||void 0===r?void 0:r.toLowerCase())&&(e.metaKey||e.ctrlKey)||!function(e){var t=e.target,n=t.tagName;return t.isContentEditable||"INPUT"===n||"SELECT"===n||"TEXTAREA"===n}(e)&&"/"===e.key&&!t)&&(e.preventDefault(),t?o():document.body.classList.contains("DocSearch--active")||document.body.classList.contains("DocSearch--active")||n()),a&&a.current===document.activeElement&&i&&/[a-zA-Z0-9]/.test(String.fromCharCode(e.keyCode))&&i(e)}return window.addEventListener("keydown",e),function(){window.removeEventListener("keydown",e)}}),[t,n,o,i,a])}},35947:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(96540),o=n(58168),i=n(53259),a=n.n(i),c=n(84054);const s={"00424e3f":[()=>n.e(4879).then(n.bind(n,465)),"@site/../docs/troubleshooting/building-the-app.md",465],"01a85c17":[()=>Promise.all([n.e(1869),n.e(8209)]).then(n.bind(n,69158)),"@theme/BlogTagsListPage",69158],"040764b7":[()=>n.e(4651).then(n.bind(n,91724)),"@site/../docs/guide/testing-webviews.md",91724],"04c33c1d":[()=>n.e(9304).then(n.bind(n,29940)),"@site/../docs/guide/cucumber-js-integration.md",29940],"04d1378b":[()=>Promise.all([n.e(1869),n.e(3282),n.e(3414)]).then(n.bind(n,63566)),"@site/versioned_docs/version-20.x/config/devices.mdx",63566],"06556991":[()=>Promise.all([n.e(1869),n.e(3890)]).then(n.bind(n,75173)),"@site/../docs/guide/uninstalling.md",75173],"090a441b":[()=>n.e(3150).then(n.bind(n,13685)),"@site/versioned_docs/version-20.x/contributing/reporting-bugs.md",13685],"0970ca0c":[()=>n.e(8560).then(n.bind(n,18779)),"@site/../docs/cli/build-framework-cache.md",18779],"0a02aff3":[()=>n.e(7924).then(n.bind(n,26530)),"@site/versioned_docs/version-19.x/APIRef.Expect.md",26530],"0a2b2829":[()=>n.e(99).then(n.bind(n,73972)),"@site/../docs/articles/how-detox-works.md",73972],"0b72a6ff":[()=>n.e(7715).then(n.bind(n,12094)),"@site/versioned_docs/version-19.x/Troubleshooting.Synchronization.md",12094],"0b851b4d":[()=>n.e(9585).then(n.bind(n,14120)),"@site/../docs/guide/android-dev-env.md",14120],"0c7edd3c":[()=>Promise.all([n.e(1869),n.e(3282),n.e(9966)]).then(n.bind(n,22505)),"@site/../docs/introduction/debugging.mdx",22505],"0d1d5dba":[()=>n.e(2124).then(n.t.bind(n,25317,19)),"~blog/default/detox-blog-tags-tags-2aa.json",25317],"0e97a833":[()=>Promise.all([n.e(1869),n.e(3015)]).then(n.bind(n,72414)),"@site/versioned_docs/version-19.x/Guide.Mocha.md",72414],"0ef19a76":[()=>n.e(3392).then(n.t.bind(n,4061,19)),"/home/runner/work/Detox/Detox/website/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",4061],"0f6ee85b":[()=>n.e(7024).then(n.bind(n,84327)),"@site/../docs/api/webviews.md",84327],"120e2fab":[()=>n.e(3894).then(n.bind(n,83817)),"@site/versioned_docs/version-19.x/Introduction.Android.md",83817],"121cced5":[()=>n.e(96).then(n.bind(n,69768)),"@site/versioned_docs/version-19.x/Troubleshooting.RunningTests.md",69768],"1373a77b":[()=>Promise.all([n.e(1869),n.e(682)]).then(n.bind(n,72743)),"@site/../docs/guide/migration.md",72743],"155ec335":[()=>n.e(4005).then(n.bind(n,86394)),"@site/../docs/api/system.md",86394],"170ab94c":[()=>n.e(7714).then(n.bind(n,64200)),"@site/versioned_docs/version-19.x/APIRef.Matchers.md",64200],17896441:[()=>Promise.all([n.e(1869),n.e(3282),n.e(9551),n.e(8401)]).then(n.bind(n,25022)),"@theme/DocItem",25022],"17f9232f":[()=>n.e(9437).then(n.bind(n,75697)),"@site/../docs/contributing/questions/answering-questions.md",75697],"181d712d":[()=>n.e(9685).then(n.t.bind(n,52945,19)),"/home/runner/work/Detox/Detox/website/.docusaurus/docusaurus-plugin-content-blog/default/plugin-route-context-module-100.json",52945],"18fc5556":[()=>n.e(2).then(n.bind(n,8788)),"@site/versioned_docs/version-20.x/contributing/code/building-and-testing.md",8788],"19ee85cd":[()=>n.e(6887).then(n.bind(n,36972)),"@site/../docs/config/overview.mdx",36972],"1a01fdf1":[()=>Promise.all([n.e(1869),n.e(6769)]).then(n.bind(n,44229)),"@site/../docs/api/internals.mdx",44229],"1a4e3797":[()=>Promise.all([n.e(1869),n.e(2138)]).then(n.bind(n,74604)),"@theme/SearchPage",74604],"1c323773":[()=>n.e(8620).then(n.bind(n,67310)),"@site/versioned_docs/version-19.x/Guide.Expo.md",67310],"1c545b73":[()=>n.e(1079).then(n.bind(n,57693)),"@site/../docs/contributing/code-of-conduct.md",57693],"1d245f66":[()=>n.e(9591).then(n.bind(n,88762)),"@site/versioned_docs/version-19.x/Introduction.WritingFirstTest.md",88762],"1ee22d16":[()=>n.e(6291).then(n.bind(n,70308)),"@site/versioned_docs/version-20.x/api/system.md",70308],"20110cf9":[()=>n.e(643).then(n.bind(n,61575)),"@site/versioned_docs/version-20.x/api/logger.mdx",61575],"239f6efa":[()=>n.e(9679).then(n.bind(n,71690)),"@site/versioned_docs/version-19.x/Guide.RunningOnCI.md",71690],"23a61782":[()=>n.e(7321).then(n.bind(n,31904)),"@site/../docs/guide/launch-args.md",31904],24602229:[()=>n.e(3892).then(n.bind(n,87992)),"@site/versioned_docs/version-19.x/Introduction.HowDetoxWorks.md",87992],26368098:[()=>n.e(1211).then(n.bind(n,83093)),"@site/../docs/guide/developing-while-writing-tests.md",83093],"268dddcf":[()=>n.e(68).then(n.bind(n,17704)),"@site/../docs/cli/rebuild-framework-cache.md",17704],"289d965c":[()=>n.e(6643).then(n.bind(n,97348)),"@site/../docs/guide/proguard-configuration.md",97348],"2aa9b0dd":[()=>Promise.all([n.e(1869),n.e(8160)]).then(n.bind(n,6719)),"@site/versioned_docs/version-20.x/contributing/code/setting-up-the-dev-environment.md",6719],"2ad854c4":[()=>n.e(3650).then(n.t.bind(n,86049,19)),"~blog/default/detox-blog-tags-detox-copilot-3ae-list.json",86049],"2cf03b61":[()=>n.e(8952).then(n.bind(n,73983)),"@site/versioned_docs/version-20.x/cli/test.md",73983],"2d84255a":[()=>Promise.all([n.e(1869),n.e(3282),n.e(7608)]).then(n.bind(n,39936)),"@site/../docs/config/devices.mdx",39936],"2dcc617a":[()=>Promise.all([n.e(1869),n.e(3282),n.e(2120)]).then(n.bind(n,21141)),"@site/versioned_docs/version-20.x/config/behavior.mdx",21141],"2e9ac6cb":[()=>n.e(498).then(n.bind(n,10623)),"@site/../docs/api/copilot.md",10623],"2ec4e639":[()=>n.e(5104).then(n.bind(n,81110)),"@site/versioned_docs/version-19.x/config/overview.md",81110],"30a967f3":[()=>Promise.all([n.e(1869),n.e(3282),n.e(5484)]).then(n.bind(n,69945)),"@site/versioned_docs/version-20.x/demo.mdx",69945],"328083ea":[()=>n.e(1581).then(n.bind(n,54806)),"@site/versioned_docs/version-20.x/contributing/questions/asking-questions.md",54806],"329947da":[()=>n.e(5844).then(n.bind(n,4317)),"@site/versioned_docs/version-20.x/api/webviews.md",4317],"32a9b7bf":[()=>n.e(6527).then(n.bind(n,25752)),"@site/versioned_docs/version-19.x/APIRef.Artifacts.md",25752],"3498d2db":[()=>n.e(8641).then(n.bind(n,32518)),"@site/versioned_docs/version-19.x/Guide.DebuggingInAndroidStudio.md",32518],"373c35af":[()=>n.e(2698).then(n.bind(n,49343)),"@site/versioned_docs/version-20.x/api/device.md",49343],"37fdb427":[()=>n.e(2460).then(n.bind(n,38502)),"@site/../docs/contributing/code/building-and-testing.md",38502],"38bf2aac":[()=>Promise.all([n.e(1869),n.e(3282),n.e(2377)]).then(n.bind(n,3723)),"@site/versioned_docs/version-20.x/introduction/debugging.mdx",3723],"3974811d":[()=>Promise.all([n.e(1869),n.e(7696)]).then(n.bind(n,28350)),"@site/versioned_docs/version-19.x/Guide.Contributing.md",28350],"398b3246":[()=>n.e(2920).then(n.bind(n,92317)),"@site/../docs/api/device.md",92317],"3abc4359":[()=>Promise.all([n.e(1869),n.e(5599)]).then(n.bind(n,4732)),"@site/versioned_docs/version-19.x/Guide.Uninstalling.md",4732],"3b089002":[()=>n.e(8881).then(n.bind(n,87507)),"@site/../docs/troubleshooting/flakiness.md",87507],"3bc305d8":[()=>n.e(123).then(n.bind(n,1748)),"@site/../docs/guide/mocking-user-notifications.md",1748],"3c59129e":[()=>n.e(5992).then(n.bind(n,78675)),"@site/versioned_docs/version-20.x/contributing/questions/answering-questions.md",78675],"3d4c33f6":[()=>n.e(2083).then(n.bind(n,41329)),"@site/versioned_docs/version-20.x/cli/reset-lock-file.md",41329],"3e7ee0fc":[()=>n.e(2965).then(n.bind(n,98218)),"@site/versioned_docs/version-20.x/cli/rebuild-framework-cache.md",98218],"3f3efe2b":[()=>Promise.all([n.e(1869),n.e(3282),n.e(3671)]).then(n.bind(n,90551)),"@site/../docs/config/testRunner.mdx",90551],"3f50474f":[()=>n.e(4930).then(n.bind(n,6645)),"@site/versioned_docs/version-19.x/Guide.DevelopingWhileWritingTests.md",6645],"3f859f05":[()=>n.e(2269).then(n.bind(n,42329)),"@site/versioned_docs/version-20.x/guide/genymotion-saas.mdx",42329],"3fb5a56d":[()=>n.e(1539).then(n.t.bind(n,61966,19)),"/home/runner/work/Detox/Detox/website/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",61966],"40113ee6":[()=>Promise.all([n.e(1869),n.e(3282),n.e(7056)]).then(n.bind(n,36137)),"@site/versioned_docs/version-20.x/introduction/project-setup.mdx",36137],"40e30cb7":[()=>n.e(8221).then(n.bind(n,54988)),"@site/versioned_docs/version-20.x/troubleshooting/running-tests.md",54988],"41e12717":[()=>n.e(6913).then(n.bind(n,19696)),"@site/versioned_docs/version-19.x/Introduction.Workflows.md",19696],"435645a6":[()=>n.e(2661).then(n.bind(n,20767)),"@site/versioned_docs/version-20.x/guide/test-id.md",20767],"45e366b7":[()=>Promise.all([n.e(1869),n.e(3282),n.e(187)]).then(n.bind(n,1145)),"@site/versioned_docs/version-20.x/config/testRunner.mdx",1145],"478bcf42":[()=>Promise.all([n.e(1869),n.e(7619)]).then(n.bind(n,95297)),"@site/versioned_docs/version-20.x/contributing/documentation.md",95297],"4898f926":[()=>n.e(3559).then(n.bind(n,11735)),"@site/versioned_docs/version-20.x/guide/developing-while-writing-tests.md",11735],"48c4a2a0":[()=>n.e(7308).then(n.t.bind(n,57757,19)),"/home/runner/work/Detox/Detox/website/.docusaurus/docusaurus-theme-search-algolia/default/plugin-route-context-module-100.json",57757],"4a184f52":[()=>n.e(5210).then(n.bind(n,52438)),"@site/../docs/contributing/feature-requests.md",52438],"4aba33e7":[()=>n.e(9543).then(n.bind(n,22687)),"@site/versioned_docs/version-19.x/Introduction.DesignPrinciples.md",22687],"4afa4b8a":[()=>n.e(2438).then(n.bind(n,1015)),"@site/versioned_docs/version-19.x/Introduction.iOS.md",1015],"4c76b531":[()=>n.e(7900).then(n.bind(n,52001)),"@site/versioned_docs/version-19.x/Introduction.iOSDevEnv.md",52001],"4d2064e8":[()=>n.e(4480).then(n.bind(n,80900)),"@site/versioned_docs/version-20.x/contributing/code/submitting-pull-requests.md",80900],"4e8770ad":[()=>Promise.all([n.e(1869),n.e(3282),n.e(2043)]).then(n.bind(n,91823)),"@site/versioned_docs/version-20.x/config/session.mdx",91823],"50214cd4":[()=>Promise.all([n.e(1869),n.e(4216)]).then(n.bind(n,65600)),"@site/versioned_docs/version-19.x/Guide.Migration.md",65600],"5125c427":[()=>n.e(5458).then(n.bind(n,2463)),"@site/../docs/cli/reset-lock-file.md",2463],"5133b137":[()=>n.e(1998).then(n.bind(n,80720)),"@site/../docs/contributing/code/example-projects.md",80720],"5329dae7":[()=>n.e(1185).then(n.bind(n,6681)),"@site/../docs/copilot/testing-with-copilot.md",6681],"54c48e38":[()=>n.e(7109).then(n.bind(n,84387)),"@site/../docs/guide/mocking-user-activity.md",84387],"5a9cd6fb":[()=>n.e(7243).then(n.bind(n,81168)),"@site/../docs/introduction/preparing-for-ci.md",81168],"5b12c1a9":[()=>n.e(7577).then(n.bind(n,82038)),"@site/versioned_docs/version-20.x/config/overview.mdx",82038],"5bc90040":[()=>n.e(1218).then(n.bind(n,64657)),"@site/../docs/api/logger.mdx",64657],"5bfbde57":[()=>Promise.all([n.e(1869),n.e(7899)]).then(n.bind(n,25207)),"@site/versioned_docs/version-19.x/introduction/getting-started.md",25207],"5d002dae":[()=>n.e(2068).then(n.bind(n,66913)),"@site/../docs/guide/test-id.md",66913],"5d2f6d16":[()=>n.e(9515).then(n.bind(n,83199)),"@site/../docs/cli/init.md",83199],"5e26652b":[()=>Promise.all([n.e(1869),n.e(7126)]).then(n.bind(n,39554)),"@site/../docs/cli/overview.md",39554],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"5eff386f":[()=>n.e(9592).then(n.bind(n,70359)),"@site/versioned_docs/version-20.x/troubleshooting/building-the-app.md",70359],"5fc994c2":[()=>Promise.all([n.e(1869),n.e(4098),n.e(1874)]).then(n.bind(n,22515)),"@site/src/pages/showcase.js",22515],"5fcf77d7":[()=>n.e(5564).then(n.bind(n,41485)),"@site/../docs/guide/mocking.md",41485],"63371bf1":[()=>n.e(9118).then(n.bind(n,87718)),"@site/versioned_docs/version-20.x/contributing/code/overview.md",87718],"649c4fa3":[()=>n.e(2459).then(n.bind(n,82882)),"@site/versioned_docs/version-20.x/guide/proguard-configuration.md",82882],"656a21b7":[()=>n.e(7813).then(n.bind(n,74601)),"@site/versioned_docs/version-20.x/guide/mocking-user-activity.md",74601],"66491fb6":[()=>n.e(1839).then(n.t.bind(n,6610,19)),"~blog/default/detox-blog-tags-genymotion-f0f-list.json",6610],"66c89031":[()=>n.e(4261).then(n.bind(n,79171)),"@site/../docs/guide/taking-screenshots.md",79171],"6781c826":[()=>n.e(6002).then(n.bind(n,51182)),"@site/../docs/api/expect.md",51182],"684e0aea":[()=>n.e(5403).then(n.bind(n,5808)),"@site/versioned_docs/version-20.x/cli/clean-framework-cache.md",5808],"6875c492":[()=>Promise.all([n.e(1869),n.e(3282),n.e(9551),n.e(8382),n.e(4813)]).then(n.bind(n,33069)),"@theme/BlogTagsPostsPage",33069],"6af18994":[()=>n.e(7153).then(n.bind(n,34673)),"@site/versioned_docs/version-20.x/guide/taking-screenshots.md",34673],"6e1398c4":[()=>n.e(3182).then(n.bind(n,70191)),"@site/versioned_docs/version-19.x/Introduction.AndroidDevEnv.md",70191],"6fb4057b":[()=>Promise.all([n.e(1869),n.e(347)]).then(n.bind(n,93008)),"@site/../docs/articles/third-party-drivers.md",93008],"7358fe14":[()=>Promise.all([n.e(1869),n.e(3818)]).then(n.bind(n,47276)),"@site/versioned_docs/version-20.x/cli/overview.md",47276],"73f08e0f":[()=>n.e(3336).then(n.bind(n,73837)),"@site/blog/2024-09-30-detox-copilot-is-out.md?truncated=true",73837],"743699a6":[()=>Promise.all([n.e(1869),n.e(5333)]).then(n.bind(n,52344)),"@site/versioned_docs/version-19.x/Guide.Jest.md",52344],"74a579df":[()=>Promise.all([n.e(1869),n.e(3206)]).then(n.bind(n,46390)),"@site/versioned_docs/version-20.x/guide/typescript.md",46390],"750255a9":[()=>n.e(6101).then(n.bind(n,92492)),"@site/versioned_docs/version-20.x/contributing/code/reviewing-pull-requests.md",92492],"79ac7843":[()=>n.e(6417).then(n.bind(n,49114)),"@site/versioned_docs/version-19.x/README.md",49114],"7a99341f":[()=>Promise.all([n.e(1869),n.e(3282),n.e(2373)]).then(n.bind(n,60375)),"@site/versioned_docs/version-20.x/config/logger.mdx",60375],"7accbb75":[()=>n.e(7473).then(n.bind(n,16402)),"@site/versioned_docs/version-20.x/api/actions.md",16402],"7b8d824d":[()=>n.e(2338).then(n.bind(n,22066)),"@site/../docs/troubleshooting/running-tests.md",22066],"7c823085":[()=>n.e(8519).then(n.bind(n,73705)),"@site/../docs/troubleshooting/synchronization.md",73705],"7c97e9a2":[()=>Promise.all([n.e(1869),n.e(9342)]).then(n.bind(n,3409)),"@site/versioned_docs/version-20.x/guide/migration.md",3409],"7d2f6bd9":[()=>n.e(7839).then(n.bind(n,78704)),"@site/versioned_docs/version-20.x/guide/parallel-test-execution.md",78704],"7dcc0419":[()=>n.e(7746).then(n.bind(n,20859)),"@site/versioned_docs/version-20.x/guide/mocking.md",20859],"80f9953e":[()=>n.e(9582).then(n.bind(n,34784)),"@site/versioned_docs/version-19.x/APIRef.DetoxObjectAPI.md",34784],"814f3328":[()=>n.e(7472).then(n.t.bind(n,55513,19)),"~blog/default/blog-post-list-prop-default.json",55513],"851303a1":[()=>Promise.all([n.e(1869),n.e(3282),n.e(5763)]).then(n.bind(n,11113)),"@site/../docs/config/apps.mdx",11113],"85a74db3":[()=>Promise.all([n.e(1869),n.e(4871)]).then(n.bind(n,32535)),"@site/../docs/contributing/documentation.md",32535],"85addd09":[()=>n.e(2496).then(n.bind(n,31404)),"@site/versioned_docs/version-20.x/articles/design-principles.md",31404],"85c7de33":[()=>n.e(235).then(n.bind(n,88287)),"@site/versioned_docs/version-19.x/APIRef.MockingUserActivity.md",88287],"861bdbb6":[()=>Promise.all([n.e(1869),n.e(178)]).then(n.bind(n,8303)),"@site/versioned_docs/version-19.x/APIRef.DetoxCLI.md",8303],"865d1447":[()=>n.e(7886).then(n.bind(n,45607)),"@site/versioned_docs/version-19.x/Guide.RunningLocally.md",45607],"86a9c212":[()=>n.e(641).then(n.bind(n,47494)),"@site/../docs/cli/clean-framework-cache.md",47494],"881ac13d":[()=>n.e(8768).then(n.bind(n,61540)),"@site/../docs/api/actions.md",61540],"8ab53c77":[()=>n.e(1513).then(n.bind(n,71316)),"@site/versioned_docs/version-20.x/troubleshooting/artifacts.md",71316],"8e7c33d6":[()=>n.e(6104).then(n.bind(n,71293)),"@site/../docs/cli/test.md",71293],"8f43d633":[()=>n.e(6990).then(n.t.bind(n,82885,19)),"~blog/default/detox-blog-archive-d9f.json",82885],"9017a355":[()=>n.e(2154).then(n.bind(n,30090)),"@site/../docs/articles/design-principles.md",30090],"90ca9965":[()=>n.e(7529).then(n.t.bind(n,7899,19)),"~blog/default/detox-blog-tags-major-release-66e-list.json",7899],"9292650b":[()=>n.e(9034).then(n.bind(n,97742)),"@site/blog/2022-11-10-detox-20-is-out.md?truncated=true",97742],"9338ecde":[()=>Promise.all([n.e(1869),n.e(4203)]).then(n.bind(n,86815)),"@site/../docs/introduction/environment-setup.md",86815],"933bed1e":[()=>n.e(2141).then(n.t.bind(n,52401,19)),"~blog/default/detox-blog-0bb.json",52401],"935f2afb":[()=>n.e(8581).then(n.t.bind(n,35610,19)),"~docs/default/version-current-metadata-prop-751.json",35610],"9509e94f":[()=>n.e(708).then(n.bind(n,53459)),"@site/versioned_docs/version-20.x/troubleshooting/synchronization.md",53459],"95ce696f":[()=>n.e(6118).then(n.bind(n,88515)),"@site/../docs/copilot/technical-overview.mdx",88515],96997528:[()=>n.e(1138).then(n.bind(n,76018)),"@site/versioned_docs/version-20.x/api/matchers.md",76018],"9a34b858":[()=>Promise.all([n.e(1869),n.e(1533)]).then(n.bind(n,94836)),"@site/versioned_docs/version-20.x/api/internals.mdx",94836],"9a6ca8b8":[()=>Promise.all([n.e(1869),n.e(3282),n.e(289)]).then(n.bind(n,18918)),"@site/versioned_docs/version-20.x/config/artifacts.mdx",18918],"9b67fd78":[()=>n.e(4324).then(n.bind(n,21678)),"@site/../docs/contributing/code/submitting-pull-requests.md",21678],"9e4087bc":[()=>n.e(2711).then(n.bind(n,89331)),"@theme/BlogArchivePage",89331],"9eb50243":[()=>n.e(2419).then(n.t.bind(n,88788,19)),"~blog/default/detox-blog-tags-major-release-66e.json",88788],"9f23071e":[()=>n.e(7606).then(n.bind(n,28739)),"@site/versioned_docs/version-20.x/contributing/code-of-conduct.md",28739],a074e23a:[()=>n.e(7484).then(n.bind(n,20710)),"@site/../docs/copilot/best-practices.md",20710],a4761f6f:[()=>Promise.all([n.e(1869),n.e(3282),n.e(298)]).then(n.bind(n,11563)),"@site/versioned_docs/version-20.x/config/apps.mdx",11563],a55aca30:[()=>n.e(3010).then(n.bind(n,45413)),"@site/../docs/cli/start.md",45413],a622d695:[()=>n.e(4778).then(n.bind(n,81676)),"@site/versioned_docs/version-20.x/api/expect.md",81676],a6860cb2:[()=>n.e(4511).then(n.bind(n,84651)),"@site/../docs/contributing/reporting-bugs.md",84651],a6aa9e1f:[()=>Promise.all([n.e(1869),n.e(3282),n.e(9551),n.e(8382),n.e(7643)]).then(n.bind(n,77785)),"@theme/BlogListPage",77785],a8a1de49:[()=>Promise.all([n.e(1869),n.e(6618)]).then(n.bind(n,53046)),"@site/src/components/CustomLayout",53046],ae694851:[()=>Promise.all([n.e(1869),n.e(5928)]).then(n.bind(n,33511)),"@site/versioned_docs/version-20.x/guide/uninstalling.md",33511],af558054:[()=>n.e(7583).then(n.bind(n,14994)),"@site/versioned_docs/version-19.x/APIRef.Screenshots.md",14994],af67069a:[()=>Promise.all([n.e(1869),n.e(3282),n.e(2247)]).then(n.bind(n,21644)),"@site/versioned_docs/version-20.x/introduction/your-first-test.mdx",21644],af952e90:[()=>n.e(7360).then(n.bind(n,72504)),"@site/../docs/contributing/code/overview.md",72504],b1c3f2c7:[()=>n.e(612).then(n.t.bind(n,33939,19)),"~blog/default/detox-blog-tags-genymotion-f0f.json",33939],b48043f7:[()=>n.e(5235).then(n.bind(n,32117)),"@site/versioned_docs/version-20.x/cli/recorder.md",32117],b49de2ad:[()=>n.e(3372).then(n.bind(n,76314)),"@site/versioned_docs/version-19.x/Guide.DebuggingInXcode.md",76314],b6072cb9:[()=>n.e(8634).then(n.bind(n,62532)),"@site/versioned_docs/version-19.x/APIRef.MockingOpenWithURL.md",62532],b80a1ac9:[()=>Promise.all([n.e(1869),n.e(6938)]).then(n.bind(n,32764)),"@site/../docs/guide/typescript.md",32764],b9d5de69:[()=>Promise.all([n.e(1869),n.e(3282),n.e(8271)]).then(n.bind(n,8057)),"@site/../docs/config/session.mdx",8057],ba1e31c2:[()=>n.e(7761).then(n.bind(n,96916)),"@site/../docs/api/matchers.md",96916],baa98f43:[()=>n.e(4151).then(n.t.bind(n,21826,19)),"~blog/default/detox-blog-tags-ai-integration-5be-list.json",21826],bab8a798:[()=>n.e(3421).then(n.bind(n,37417)),"@site/versioned_docs/version-20.x/introduction/getting-started.mdx",37417],bc243f9c:[()=>n.e(4099).then(n.bind(n,58920)),"@site/versioned_docs/version-19.x/APIRef.DeviceObjectAPI.md",58920],beec6c9a:[()=>n.e(3995).then(n.bind(n,59648)),"@site/versioned_docs/version-20.x/cli/run-server.md",59648],c12557ec:[()=>n.e(3360).then(n.bind(n,75078)),"@site/../docs/contributing/code/reviewing-pull-requests.md",75078],c262e01e:[()=>n.e(3833).then(n.bind(n,59719)),"@site/../docs/introduction/getting-started.mdx",59719],c4f5d8e4:[()=>Promise.all([n.e(1869),n.e(2634)]).then(n.bind(n,83414)),"@site/src/pages/index.js",83414],c50db514:[()=>n.e(2707).then(n.bind(n,22898)),"@site/versioned_docs/version-20.x/guide/testing-webviews.md",22898],c62bfd53:[()=>n.e(2270).then(n.bind(n,23983)),"@site/versioned_docs/version-19.x/APIRef.TestLifecycle.md",23983],c74a7097:[()=>n.e(4557).then(n.bind(n,28661)),"@site/versioned_docs/version-20.x/troubleshooting/flakiness.md",28661],c7632c1f:[()=>Promise.all([n.e(1869),n.e(6388)]).then(n.bind(n,85909)),"@site/../docs/contributing/code/setting-up-the-dev-environment.md",85909],c7f75652:[()=>n.e(8716).then(n.bind(n,59547)),"@site/blog/2024-09-30-detox-copilot-is-out.md",59547],c83bcda6:[()=>n.e(6461).then(n.bind(n,59347)),"@site/../docs/guide/mocking-open-with-url.md",59347],c8447d5b:[()=>n.e(4122).then(n.bind(n,4353)),"@site/../docs/cli/build.md",4353],cae448e5:[()=>n.e(3486).then(n.bind(n,46978)),"@site/../docs/guide/parallel-test-execution.md",46978],cb38042d:[()=>n.e(323).then(n.bind(n,3367)),"@site/versioned_docs/version-20.x/cli/start.md",3367],ccc49370:[()=>Promise.all([n.e(1869),n.e(3282),n.e(9551),n.e(8382),n.e(3249)]).then(n.bind(n,84029)),"@theme/BlogPostPage",84029],cd796466:[()=>Promise.all([n.e(1869),n.e(3282),n.e(6044)]).then(n.bind(n,87081)),"@site/../docs/introduction/your-first-test.mdx",87081],d350cff3:[()=>n.e(2525).then(n.t.bind(n,84563,19)),"~docs/default/version-20-x-metadata-prop-424.json",84563],d389a7b2:[()=>n.e(5465).then(n.bind(n,78758)),"@site/../docs/troubleshooting/artifacts.md",78758],d8b689b0:[()=>Promise.all([n.e(1869),n.e(656)]).then(n.bind(n,44896)),"@site/versioned_docs/version-20.x/introduction/environment-setup.md",44896],d9683343:[()=>n.e(8245).then(n.bind(n,51730)),"@site/../docs/cli/run-server.md",51730],d96edd44:[()=>n.e(5237).then(n.t.bind(n,46595,19)),"~blog/default/detox-blog-tags-ai-integration-5be.json",46595],d9894068:[()=>n.e(577).then(n.bind(n,50928)),"@site/versioned_docs/version-20.x/contributing.md",50928],dbe4f0b7:[()=>n.e(8877).then(n.bind(n,5266)),"@site/versioned_docs/version-20.x/guide/cucumber-js-integration.md",5266],dc701447:[()=>n.e(2581).then(n.bind(n,93094)),"@site/versioned_docs/version-20.x/introduction/preparing-for-ci.md",93094],dcfd3b61:[()=>n.e(7742).then(n.bind(n,45473)),"@site/versioned_docs/version-19.x/APIRef.LaunchArgs.md",45473],dd5377a3:[()=>n.e(7846).then(n.bind(n,20593)),"@site/versioned_docs/version-20.x/cli/build-framework-cache.md",20593],ddab1e1f:[()=>Promise.all([n.e(1869),n.e(3282),n.e(2905)]).then(n.bind(n,20220)),"@site/../docs/config/artifacts.mdx",20220],ddb15f76:[()=>n.e(5110).then(n.bind(n,28199)),"@site/versioned_docs/version-20.x/cli/build.md",28199],df0b489b:[()=>n.e(770).then(n.bind(n,29768)),"@site/versioned_docs/version-19.x/APIRef.MockingUserNotifications.md",29768],df5bc064:[()=>n.e(5351).then(n.bind(n,8890)),"@site/versioned_docs/version-19.x/Guide.Mocking.md",8890],df8c2417:[()=>n.e(9120).then(n.bind(n,20721)),"@site/versioned_docs/version-19.x/APIRef.ActionsOnElement.md",20721],e237dedf:[()=>n.e(8795).then(n.bind(n,52272)),"@site/versioned_docs/version-19.x/Troubleshooting.md",52272],e2c7edfe:[()=>n.e(9736).then(n.t.bind(n,13131,19)),"~blog/default/detox-blog-tags-minor-release-46f.json",13131],e432d2f9:[()=>n.e(3134).then(n.bind(n,43516)),"@site/versioned_docs/version-19.x/Troubleshooting.Flakiness.md",43516],e453af6e:[()=>n.e(6646).then(n.bind(n,77274)),"@site/versioned_docs/version-20.x/articles/how-detox-works.md",77274],e6bddadc:[()=>n.e(3022).then(n.bind(n,29689)),"@site/versioned_docs/version-20.x/cli/init.md",29689],e6cdcb35:[()=>n.e(332).then(n.bind(n,98919)),"@site/../docs/guide/genymotion-saas.mdx",98919],e8bfc54f:[()=>Promise.all([n.e(1869),n.e(3282),n.e(4078)]).then(n.bind(n,62031)),"@site/../docs/config/behavior.mdx",62031],e9d07a0a:[()=>Promise.all([n.e(1869),n.e(3282),n.e(456)]).then(n.bind(n,30923)),"@site/../docs/demo.mdx",30923],ea7b1b31:[()=>n.e(7367).then(n.bind(n,3795)),"@site/versioned_docs/version-20.x/guide/investigating-test-failure.mdx",3795],ea9c95e7:[()=>n.e(8839).then(n.bind(n,54981)),"@site/../docs/guide/investigating-test-failure.mdx",54981],eb35abdd:[()=>n.e(8455).then(n.bind(n,28171)),"@site/../docs/cli/recorder.md",28171],ecfe08ed:[()=>n.e(1987).then(n.bind(n,77102)),"@site/../docs/contributing.md",77102],ede8d976:[()=>n.e(265).then(n.t.bind(n,73498,19)),"~blog/default/detox-blog-tags-detox-copilot-3ae.json",73498],ef7da448:[()=>Promise.all([n.e(1869),n.e(4368)]).then(n.bind(n,5310)),"@site/versioned_docs/version-20.x/articles/third-party-drivers.md",5310],f02f7df4:[()=>Promise.all([n.e(1869),n.e(3282),n.e(1274)]).then(n.bind(n,40012)),"@site/../docs/introduction/project-setup.mdx",40012],f14c3b1d:[()=>n.e(3119).then(n.t.bind(n,76854,19)),"~docs/default/version-19-x-metadata-prop-607.json",76854],f164116d:[()=>Promise.all([n.e(1869),n.e(8758)]).then(n.bind(n,36558)),"@site/versioned_docs/version-19.x/Guide.ThirdPartyDrivers.md",36558],f2f4b8a7:[()=>n.e(3103).then(n.bind(n,82322)),"@site/versioned_docs/version-20.x/guide/mocking-user-notifications.md",82322],f431fa1f:[()=>n.e(1707).then(n.bind(n,10878)),"@site/versioned_docs/version-20.x/guide/launch-args.md",10878],f6b2bbb1:[()=>Promise.all([n.e(1869),n.e(3282),n.e(8476)]).then(n.bind(n,93281)),"@site/../docs/config/logger.mdx",93281],f6c890be:[()=>n.e(5930).then(n.t.bind(n,6794,19)),"~blog/default/detox-blog-tags-minor-release-46f-list.json",6794],f9078c13:[()=>n.e(4196).then(n.bind(n,30859)),"@site/versioned_docs/version-19.x/Guide.ParallelTestExecution.md",30859],f97fefc4:[()=>n.e(3379).then(n.bind(n,25689)),"@site/versioned_docs/version-20.x/guide/mocking-open-with-url.md",25689],f98b7248:[()=>n.e(6941).then(n.bind(n,44090)),"@site/blog/2022-11-10-detox-20-is-out.md",44090],f999fa4a:[()=>n.e(9812).then(n.bind(n,63782)),"@site/versioned_docs/version-20.x/contributing/code/example-projects.md",63782],fa42474c:[()=>n.e(4034).then(n.bind(n,4922)),"@site/versioned_docs/version-19.x/Troubleshooting.BuildingTheApp.md",4922],fc323215:[()=>n.e(8460).then(n.bind(n,88376)),"@site/../docs/contributing/questions/asking-questions.md",88376],fc9f0a8f:[()=>n.e(1700).then(n.bind(n,63418)),"@site/versioned_docs/version-20.x/guide/android-dev-env.md",63418],fe23c957:[()=>n.e(53).then(n.bind(n,12020)),"@site/versioned_docs/version-20.x/contributing/feature-requests.md",12020]};function l(e){let{error:t,retry:n,pastDelay:o}=e;return t?r.createElement("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"}},r.createElement("p",null,String(t)),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},"Retry"))):o?r.createElement("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"}},r.createElement("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb"},r.createElement("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2"},r.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},r.createElement("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),r.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},r.createElement("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),r.createElement("circle",{cx:"22",cy:"22",r:"8"},r.createElement("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"}))))):null}var u=n(86921),d=n(53102);function f(e,t){if("*"===e)return a()({loading:l,loader:()=>n.e(1774).then(n.bind(n,81774)),modules:["@theme/NotFound"],webpack:()=>[81774],render(e,t){const n=e.default;return r.createElement(d.W,{value:{plugin:{name:"native",id:"default"}}},r.createElement(n,t))}});const i=c[`${e}-${t}`],f={},p=[],h=[],m=(0,u.A)(i);return Object.entries(m).forEach((e=>{let[t,n]=e;const r=s[n];r&&(f[t]=r[0],p.push(r[1]),h.push(r[2]))})),a().Map({loading:l,loader:f,modules:p,webpack:()=>h,render(t,n){const a=JSON.parse(JSON.stringify(i));Object.entries(t).forEach((t=>{let[n,r]=t;const o=r.default;if(!o)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof o&&"function"!=typeof o||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{o[e]=r[e]}));let i=a;const c=n.split(".");c.slice(0,-1).forEach((e=>{i=i[e]})),i[c[c.length-1]]=o}));const c=a.__comp;delete a.__comp;const s=a.__context;return delete a.__context,r.createElement(d.W,{value:s},r.createElement(c,(0,o.A)({},a,n)))}})}const p=[{path:"/Detox/blog",component:f("/Detox/blog","c30"),exact:!0},{path:"/Detox/blog/2022/11/10/detox-20-is-out",component:f("/Detox/blog/2022/11/10/detox-20-is-out","04b"),exact:!0},{path:"/Detox/blog/2024/09/30/detox-copilot-is-out",component:f("/Detox/blog/2024/09/30/detox-copilot-is-out","902"),exact:!0},{path:"/Detox/blog/archive",component:f("/Detox/blog/archive","723"),exact:!0},{path:"/Detox/blog/tags",component:f("/Detox/blog/tags","fe8"),exact:!0},{path:"/Detox/blog/tags/ai-integration",component:f("/Detox/blog/tags/ai-integration","e0e"),exact:!0},{path:"/Detox/blog/tags/detox-copilot",component:f("/Detox/blog/tags/detox-copilot","05e"),exact:!0},{path:"/Detox/blog/tags/genymotion",component:f("/Detox/blog/tags/genymotion","75d"),exact:!0},{path:"/Detox/blog/tags/major-release",component:f("/Detox/blog/tags/major-release","328"),exact:!0},{path:"/Detox/blog/tags/minor-release",component:f("/Detox/blog/tags/minor-release","f51"),exact:!0},{path:"/Detox/search",component:f("/Detox/search","576"),exact:!0},{path:"/Detox/showcase",component:f("/Detox/showcase","d2d"),exact:!0},{path:"/Detox/docs/19.x",component:f("/Detox/docs/19.x","725"),routes:[{path:"/Detox/docs/19.x/",component:f("/Detox/docs/19.x/","4d2"),exact:!0},{path:"/Detox/docs/19.x/api/actions-on-element",component:f("/Detox/docs/19.x/api/actions-on-element","1c2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/artifacts",component:f("/Detox/docs/19.x/api/artifacts","6ac"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/detox-cli",component:f("/Detox/docs/19.x/api/detox-cli","b6e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/detox-object-api",component:f("/Detox/docs/19.x/api/detox-object-api","aa7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/device-object-api",component:f("/Detox/docs/19.x/api/device-object-api","64a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/expect",component:f("/Detox/docs/19.x/api/expect","cc4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/launch-args",component:f("/Detox/docs/19.x/api/launch-args","671"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/matchers",component:f("/Detox/docs/19.x/api/matchers","8cd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/mocking-open-with-url",component:f("/Detox/docs/19.x/api/mocking-open-with-url","116"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/mocking-user-activity",component:f("/Detox/docs/19.x/api/mocking-user-activity","cfa"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/mocking-user-notifications",component:f("/Detox/docs/19.x/api/mocking-user-notifications","725"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/screenshots",component:f("/Detox/docs/19.x/api/screenshots","bb0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/api/test-lifecycle",component:f("/Detox/docs/19.x/api/test-lifecycle","451"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/config/overview",component:f("/Detox/docs/19.x/config/overview","c73"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/contributing",component:f("/Detox/docs/19.x/contributing","04b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/debugging-in-android-studio",component:f("/Detox/docs/19.x/guide/debugging-in-android-studio","1a7"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/debugging-in-xcode",component:f("/Detox/docs/19.x/guide/debugging-in-xcode","50c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/developing-while-writing-tests",component:f("/Detox/docs/19.x/guide/developing-while-writing-tests","121"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/expo",component:f("/Detox/docs/19.x/guide/expo","ff2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/jest",component:f("/Detox/docs/19.x/guide/jest","468"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/migration",component:f("/Detox/docs/19.x/guide/migration","9af"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/mocha",component:f("/Detox/docs/19.x/guide/mocha","224"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/mocking",component:f("/Detox/docs/19.x/guide/mocking","7b4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/parallel-test-execution",component:f("/Detox/docs/19.x/guide/parallel-test-execution","ea9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/running-locally",component:f("/Detox/docs/19.x/guide/running-locally","980"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/running-on-ci",component:f("/Detox/docs/19.x/guide/running-on-ci","57e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/third-party-drivers",component:f("/Detox/docs/19.x/guide/third-party-drivers","c97"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/guide/uninstalling",component:f("/Detox/docs/19.x/guide/uninstalling","e71"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/android",component:f("/Detox/docs/19.x/introduction/android","9dd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/android-dev-env",component:f("/Detox/docs/19.x/introduction/android-dev-env","45e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/design-principles",component:f("/Detox/docs/19.x/introduction/design-principles","780"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/getting-started",component:f("/Detox/docs/19.x/introduction/getting-started","4c6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/how-detox-works",component:f("/Detox/docs/19.x/introduction/how-detox-works","618"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/ios",component:f("/Detox/docs/19.x/introduction/ios","d94"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/ios-dev-env",component:f("/Detox/docs/19.x/introduction/ios-dev-env","4bf"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/workflows",component:f("/Detox/docs/19.x/introduction/workflows","46e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/introduction/writing-first-test",component:f("/Detox/docs/19.x/introduction/writing-first-test","091"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/troubleshooting/building-the-app",component:f("/Detox/docs/19.x/troubleshooting/building-the-app","b06"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/troubleshooting/flakiness",component:f("/Detox/docs/19.x/troubleshooting/flakiness","4ff"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/troubleshooting/running-tests",component:f("/Detox/docs/19.x/troubleshooting/running-tests","e2a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/troubleshooting/synchronization",component:f("/Detox/docs/19.x/troubleshooting/synchronization","ddd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/19.x/troubleshooting/troubleshooting",component:f("/Detox/docs/19.x/troubleshooting/troubleshooting","018"),exact:!0,sidebar:"tutorialSidebar"}]},{path:"/Detox/docs/next",component:f("/Detox/docs/next","f17"),routes:[{path:"/Detox/docs/next/api/actions",component:f("/Detox/docs/next/api/actions","cd0"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/copilot",component:f("/Detox/docs/next/api/copilot","68e"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/device",component:f("/Detox/docs/next/api/device","306"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/expect",component:f("/Detox/docs/next/api/expect","d84"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/internals",component:f("/Detox/docs/next/api/internals","a1c"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/logger",component:f("/Detox/docs/next/api/logger","4ae"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/matchers",component:f("/Detox/docs/next/api/matchers","53e"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/system",component:f("/Detox/docs/next/api/system","8ab"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/api/webviews",component:f("/Detox/docs/next/api/webviews","b0d"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/articles/design-principles",component:f("/Detox/docs/next/articles/design-principles","b6b"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/articles/how-detox-works",component:f("/Detox/docs/next/articles/how-detox-works","e9a"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/articles/third-party-drivers",component:f("/Detox/docs/next/articles/third-party-drivers","b2c"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/build",component:f("/Detox/docs/next/cli/build","a44"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/build-framework-cache",component:f("/Detox/docs/next/cli/build-framework-cache","341"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/clean-framework-cache",component:f("/Detox/docs/next/cli/clean-framework-cache","42a"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/init",component:f("/Detox/docs/next/cli/init","b35"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/overview",component:f("/Detox/docs/next/cli/overview","f53"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/rebuild-framework-cache",component:f("/Detox/docs/next/cli/rebuild-framework-cache","c65"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/recorder",component:f("/Detox/docs/next/cli/recorder","9a4"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/reset-lock-file",component:f("/Detox/docs/next/cli/reset-lock-file","78e"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/run-server",component:f("/Detox/docs/next/cli/run-server","86b"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/start",component:f("/Detox/docs/next/cli/start","f17"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/cli/test",component:f("/Detox/docs/next/cli/test","699"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/apps",component:f("/Detox/docs/next/config/apps","1d3"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/artifacts",component:f("/Detox/docs/next/config/artifacts","825"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/behavior",component:f("/Detox/docs/next/config/behavior","efc"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/devices",component:f("/Detox/docs/next/config/devices","bcd"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/logger",component:f("/Detox/docs/next/config/logger","257"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/overview",component:f("/Detox/docs/next/config/overview","802"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/session",component:f("/Detox/docs/next/config/session","a7f"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/config/testRunner",component:f("/Detox/docs/next/config/testRunner","6ec"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/next/contributing",component:f("/Detox/docs/next/contributing","739"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code-of-conduct",component:f("/Detox/docs/next/contributing/code-of-conduct","20f"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code/building-and-testing",component:f("/Detox/docs/next/contributing/code/building-and-testing","a97"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code/example-projects",component:f("/Detox/docs/next/contributing/code/example-projects","556"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code/overview",component:f("/Detox/docs/next/contributing/code/overview","998"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code/reviewing-pull-requests",component:f("/Detox/docs/next/contributing/code/reviewing-pull-requests","6de"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code/setting-up-the-dev-environment",component:f("/Detox/docs/next/contributing/code/setting-up-the-dev-environment","9be"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/code/submitting-pull-requests",component:f("/Detox/docs/next/contributing/code/submitting-pull-requests","a0f"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/documentation",component:f("/Detox/docs/next/contributing/documentation","067"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/feature-requests",component:f("/Detox/docs/next/contributing/feature-requests","6eb"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/questions/answering-questions",component:f("/Detox/docs/next/contributing/questions/answering-questions","26d"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/questions/asking-questions",component:f("/Detox/docs/next/contributing/questions/asking-questions","de0"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/contributing/reporting-bugs",component:f("/Detox/docs/next/contributing/reporting-bugs","25c"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/next/copilot/best-practices",component:f("/Detox/docs/next/copilot/best-practices","d01"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/copilot/technical-overview",component:f("/Detox/docs/next/copilot/technical-overview","d0a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/copilot/testing-with-copilot",component:f("/Detox/docs/next/copilot/testing-with-copilot","2c4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/demo",component:f("/Detox/docs/next/demo","01f"),exact:!0},{path:"/Detox/docs/next/guide/android-dev-env",component:f("/Detox/docs/next/guide/android-dev-env","841"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/cucumber-js-integration",component:f("/Detox/docs/next/guide/cucumber-js-integration","270"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/developing-while-writing-tests",component:f("/Detox/docs/next/guide/developing-while-writing-tests","be1"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/genymotion-saas",component:f("/Detox/docs/next/guide/genymotion-saas","789"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/investigating-test-failure",component:f("/Detox/docs/next/guide/investigating-test-failure","4ad"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/launch-args",component:f("/Detox/docs/next/guide/launch-args","8f4"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/migration",component:f("/Detox/docs/next/guide/migration","296"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/mocking",component:f("/Detox/docs/next/guide/mocking","d4f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/mocking-open-with-url",component:f("/Detox/docs/next/guide/mocking-open-with-url","582"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/mocking-user-activity",component:f("/Detox/docs/next/guide/mocking-user-activity","6a0"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/mocking-user-notifications",component:f("/Detox/docs/next/guide/mocking-user-notifications","69d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/parallel-test-execution",component:f("/Detox/docs/next/guide/parallel-test-execution","11f"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/proguard-configuration",component:f("/Detox/docs/next/guide/proguard-configuration","bf9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/taking-screenshots",component:f("/Detox/docs/next/guide/taking-screenshots","09b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/test-id",component:f("/Detox/docs/next/guide/test-id","abf"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/testing-webviews",component:f("/Detox/docs/next/guide/testing-webviews","64b"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/typescript",component:f("/Detox/docs/next/guide/typescript","169"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/guide/uninstalling",component:f("/Detox/docs/next/guide/uninstalling","d7c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/introduction/debugging",component:f("/Detox/docs/next/introduction/debugging","8c9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/introduction/environment-setup",component:f("/Detox/docs/next/introduction/environment-setup","315"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/introduction/getting-started",component:f("/Detox/docs/next/introduction/getting-started","859"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/introduction/preparing-for-ci",component:f("/Detox/docs/next/introduction/preparing-for-ci","214"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/introduction/project-setup",component:f("/Detox/docs/next/introduction/project-setup","a97"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/introduction/your-first-test",component:f("/Detox/docs/next/introduction/your-first-test","dc6"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/troubleshooting/artifacts",component:f("/Detox/docs/next/troubleshooting/artifacts","c54"),exact:!0},{path:"/Detox/docs/next/troubleshooting/building-the-app",component:f("/Detox/docs/next/troubleshooting/building-the-app","434"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/troubleshooting/flakiness",component:f("/Detox/docs/next/troubleshooting/flakiness","074"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/troubleshooting/running-tests",component:f("/Detox/docs/next/troubleshooting/running-tests","10a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/next/troubleshooting/synchronization",component:f("/Detox/docs/next/troubleshooting/synchronization","5b4"),exact:!0,sidebar:"tutorialSidebar"}]},{path:"/Detox/docs",component:f("/Detox/docs","071"),routes:[{path:"/Detox/docs/api/actions",component:f("/Detox/docs/api/actions","854"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/device",component:f("/Detox/docs/api/device","fe3"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/expect",component:f("/Detox/docs/api/expect","21a"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/internals",component:f("/Detox/docs/api/internals","17c"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/logger",component:f("/Detox/docs/api/logger","619"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/matchers",component:f("/Detox/docs/api/matchers","f36"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/system",component:f("/Detox/docs/api/system","ea1"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/api/webviews",component:f("/Detox/docs/api/webviews","05b"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/articles/design-principles",component:f("/Detox/docs/articles/design-principles","fdb"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/articles/how-detox-works",component:f("/Detox/docs/articles/how-detox-works","db3"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/articles/third-party-drivers",component:f("/Detox/docs/articles/third-party-drivers","e7e"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/build",component:f("/Detox/docs/cli/build","043"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/build-framework-cache",component:f("/Detox/docs/cli/build-framework-cache","34f"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/clean-framework-cache",component:f("/Detox/docs/cli/clean-framework-cache","b33"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/init",component:f("/Detox/docs/cli/init","3cd"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/overview",component:f("/Detox/docs/cli/overview","809"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/rebuild-framework-cache",component:f("/Detox/docs/cli/rebuild-framework-cache","65e"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/recorder",component:f("/Detox/docs/cli/recorder","215"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/reset-lock-file",component:f("/Detox/docs/cli/reset-lock-file","000"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/run-server",component:f("/Detox/docs/cli/run-server","aa3"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/start",component:f("/Detox/docs/cli/start","2a9"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/cli/test",component:f("/Detox/docs/cli/test","e12"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/apps",component:f("/Detox/docs/config/apps","1da"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/artifacts",component:f("/Detox/docs/config/artifacts","fb9"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/behavior",component:f("/Detox/docs/config/behavior","471"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/devices",component:f("/Detox/docs/config/devices","11a"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/logger",component:f("/Detox/docs/config/logger","bad"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/overview",component:f("/Detox/docs/config/overview","88c"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/session",component:f("/Detox/docs/config/session","214"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/config/testRunner",component:f("/Detox/docs/config/testRunner","271"),exact:!0,sidebar:"apiSidebar"},{path:"/Detox/docs/contributing",component:f("/Detox/docs/contributing","6c5"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code-of-conduct",component:f("/Detox/docs/contributing/code-of-conduct","1fe"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code/building-and-testing",component:f("/Detox/docs/contributing/code/building-and-testing","1cc"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code/example-projects",component:f("/Detox/docs/contributing/code/example-projects","3a7"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code/overview",component:f("/Detox/docs/contributing/code/overview","1ca"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code/reviewing-pull-requests",component:f("/Detox/docs/contributing/code/reviewing-pull-requests","f0c"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code/setting-up-the-dev-environment",component:f("/Detox/docs/contributing/code/setting-up-the-dev-environment","f7c"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/code/submitting-pull-requests",component:f("/Detox/docs/contributing/code/submitting-pull-requests","235"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/documentation",component:f("/Detox/docs/contributing/documentation","dc9"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/feature-requests",component:f("/Detox/docs/contributing/feature-requests","acb"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/questions/answering-questions",component:f("/Detox/docs/contributing/questions/answering-questions","af3"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/questions/asking-questions",component:f("/Detox/docs/contributing/questions/asking-questions","79a"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/contributing/reporting-bugs",component:f("/Detox/docs/contributing/reporting-bugs","76e"),exact:!0,sidebar:"contributeSidebar"},{path:"/Detox/docs/demo",component:f("/Detox/docs/demo","717"),exact:!0},{path:"/Detox/docs/guide/android-dev-env",component:f("/Detox/docs/guide/android-dev-env","483"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/cucumber-js-integration",component:f("/Detox/docs/guide/cucumber-js-integration","8bd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/developing-while-writing-tests",component:f("/Detox/docs/guide/developing-while-writing-tests","05c"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/genymotion-saas",component:f("/Detox/docs/guide/genymotion-saas","1fc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/investigating-test-failure",component:f("/Detox/docs/guide/investigating-test-failure","872"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/launch-args",component:f("/Detox/docs/guide/launch-args","a38"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/migration",component:f("/Detox/docs/guide/migration","0ad"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/mocking",component:f("/Detox/docs/guide/mocking","4bc"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/mocking-open-with-url",component:f("/Detox/docs/guide/mocking-open-with-url","a0d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/mocking-user-activity",component:f("/Detox/docs/guide/mocking-user-activity","814"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/mocking-user-notifications",component:f("/Detox/docs/guide/mocking-user-notifications","734"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/parallel-test-execution",component:f("/Detox/docs/guide/parallel-test-execution","018"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/proguard-configuration",component:f("/Detox/docs/guide/proguard-configuration","44e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/taking-screenshots",component:f("/Detox/docs/guide/taking-screenshots","0d2"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/test-id",component:f("/Detox/docs/guide/test-id","fee"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/testing-webviews",component:f("/Detox/docs/guide/testing-webviews","b0d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/typescript",component:f("/Detox/docs/guide/typescript","e33"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/guide/uninstalling",component:f("/Detox/docs/guide/uninstalling","e93"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/introduction/debugging",component:f("/Detox/docs/introduction/debugging","8ff"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/introduction/environment-setup",component:f("/Detox/docs/introduction/environment-setup","1d9"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/introduction/getting-started",component:f("/Detox/docs/introduction/getting-started","710"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/introduction/preparing-for-ci",component:f("/Detox/docs/introduction/preparing-for-ci","102"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/introduction/project-setup",component:f("/Detox/docs/introduction/project-setup","71e"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/introduction/your-first-test",component:f("/Detox/docs/introduction/your-first-test","263"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/troubleshooting/artifacts",component:f("/Detox/docs/troubleshooting/artifacts","b2c"),exact:!0},{path:"/Detox/docs/troubleshooting/building-the-app",component:f("/Detox/docs/troubleshooting/building-the-app","c23"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/troubleshooting/flakiness",component:f("/Detox/docs/troubleshooting/flakiness","85d"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/troubleshooting/running-tests",component:f("/Detox/docs/troubleshooting/running-tests","fe8"),exact:!0,sidebar:"tutorialSidebar"},{path:"/Detox/docs/troubleshooting/synchronization",component:f("/Detox/docs/troubleshooting/synchronization","a0b"),exact:!0,sidebar:"tutorialSidebar"}]},{path:"/Detox/",component:f("/Detox/","ea6"),exact:!0},{path:"*",component:f("*")}]},6125:(e,t,n)=>{"use strict";n.d(t,{o:()=>o,x:()=>i});var r=n(96540);const o=r.createContext(!1);function i(e){let{children:t}=e;const[n,i]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{i(!0)}),[]),r.createElement(o.Provider,{value:n},t)}},38536:(e,t,n)=>{"use strict";var r=n(96540),o=n(40961),i=n(54625),a=n(80545),c=n(38193);const s=[n(10119),n(26134),n(76294),n(50895)];var l=n(35947),u=n(56347),d=n(22831);function f(e){let{children:t}=e;return r.createElement(r.Fragment,null,t)}var p=n(58168),h=n(5260),m=n(44586),g=n(86025),b=n(6342),v=n(69024),y=n(32131),x=n(14090),w=n(2967),S=n(70440),_=n(41463);function E(){const{i18n:{defaultLocale:e,localeConfigs:t}}=(0,m.A)(),n=(0,y.o)();return r.createElement(h.A,null,Object.entries(t).map((e=>{let[t,{htmlLang:o}]=e;return r.createElement("link",{key:t,rel:"alternate",href:n.createUrl({locale:t,fullyQualified:!0}),hrefLang:o})})),r.createElement("link",{rel:"alternate",href:n.createUrl({locale:e,fullyQualified:!0}),hrefLang:"x-default"}))}function k(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,m.A)(),o=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,m.A)(),{pathname:r}=(0,u.zy)();return e+(0,S.applyTrailingSlash)((0,g.A)(r),{trailingSlash:n,baseUrl:t})}(),i=t?`${n}${t}`:o;return r.createElement(h.A,null,r.createElement("meta",{property:"og:url",content:i}),r.createElement("link",{rel:"canonical",href:i}))}function D(){const{i18n:{currentLocale:e}}=(0,m.A)(),{metadata:t,image:n}=(0,b.p)();return r.createElement(r.Fragment,null,r.createElement(h.A,null,r.createElement("meta",{name:"twitter:card",content:"summary_large_image"}),r.createElement("body",{className:x.w})),n&&r.createElement(v.be,{image:n}),r.createElement(k,null),r.createElement(E,null),r.createElement(_.A,{tag:w.Cy,locale:e}),r.createElement(h.A,null,t.map(((e,t)=>r.createElement("meta",(0,p.A)({key:t},e))))))}const A=new Map;function O(e){if(A.has(e.pathname))return{...e,pathname:A.get(e.pathname)};if((0,d.u)(l.A,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return A.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return A.set(e.pathname,t),{...e,pathname:t}}var C=n(6125),T=n(26988);function P(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{const r=t.default?.[e]??t[e];return r?.(...n)}));return()=>o.forEach((e=>e?.()))}const I=function(e){let{children:t,location:n,previousLocation:o}=e;return(0,r.useLayoutEffect)((()=>{o!==n&&(!function(e){let{location:t,previousLocation:n}=e;if(!n)return;const r=t.pathname===n.pathname,o=t.hash===n.hash,i=t.search===n.search;if(r&&o&&!i)return;const{hash:a}=t;if(a){const e=decodeURIComponent(a.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:o}),P("onRouteDidUpdate",{previousLocation:o,location:n}))}),[o,n]),t};function R(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,d.u)(l.A,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class N extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=c.A.canUseDOM?P("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=P("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),R(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return r.createElement(I,{previousLocation:this.previousLocation,location:t},r.createElement(u.qh,{location:t,render:()=>e}))}}const j=N,L="__docusaurus-base-url-issue-banner-container",M="__docusaurus-base-url-issue-banner-suggestion-container",B="__DOCUSAURUS_INSERT_BASEURL_BANNER";function F(e){return`\nwindow['${B}'] = true;\n\ndocument.addEventListener('DOMContentLoaded', maybeInsertBanner);\n\nfunction maybeInsertBanner() {\n var shouldInsert = window['${B}'];\n shouldInsert && insertBanner();\n}\n\nfunction insertBanner() {\n var bannerContainer = document.getElementById('${L}');\n if (!bannerContainer) {\n return;\n }\n var bannerHtml = ${JSON.stringify(function(e){return`\n
\n

Your Docusaurus site did not load properly.

\n

A very common reason is a wrong site baseUrl configuration.

\n

Current configured baseUrl = ${e} ${"/"===e?" (default value)":""}

\n

We suggest trying baseUrl =

\n
\n`}(e)).replace(/{window[B]=!1}),[]),r.createElement(r.Fragment,null,!c.A.canUseDOM&&r.createElement(h.A,null,r.createElement("script",null,F(e))),r.createElement("div",{id:L}))}function U(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,m.A)(),{pathname:n}=(0,u.zy)();return t&&n===e?r.createElement(z,null):null}function $(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:o,localeConfigs:i}}=(0,m.A)(),a=(0,g.A)(e),{htmlLang:c,direction:s}=i[o];return r.createElement(h.A,null,r.createElement("html",{lang:c,dir:s}),r.createElement("title",null,t),r.createElement("meta",{property:"og:title",content:t}),r.createElement("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&r.createElement("meta",{name:"robots",content:"noindex, nofollow"}),e&&r.createElement("link",{rel:"icon",href:a}))}var q=n(67489),H=n(92303);function V(){const e=(0,H.A)();return r.createElement(h.A,null,r.createElement("html",{"data-has-hydrated":e}))}function W(){const e=(0,d.v)(l.A),t=(0,u.zy)();return r.createElement(q.A,null,r.createElement(T.l,null,r.createElement(C.x,null,r.createElement(f,null,r.createElement($,null),r.createElement(D,null),r.createElement(U,null),r.createElement(j,{location:O(t)},e)),r.createElement(V,null))))}var G=n(84054);const K=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();const o=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;o?.appendChild(r)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var Y=n(86921);const Q=new Set,Z=new Set,X=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,J={prefetch(e){if(!(e=>!X()&&!Z.has(e)&&!Q.has(e))(e))return!1;Q.add(e);const t=(0,d.u)(l.A,e).flatMap((e=>{return t=e.route.path,Object.entries(G).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,Y.A)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?K(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!X()&&!Z.has(e))(e)&&(Z.add(e),R(e))},ee=Object.freeze(J);if(c.A.canUseDOM){window.docusaurus=ee;const e=o.hydrate;R(window.location.pathname).then((()=>{e(r.createElement(a.vd,null,r.createElement(i.Kd,null,r.createElement(W,null))),document.getElementById("__docusaurus"))}))}},26988:(e,t,n)=>{"use strict";n.d(t,{o:()=>u,l:()=>d});var r=n(96540),o=n(4784);const i=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/Detox/docs","versions":[{"name":"current","label":"Next","isLast":false,"path":"/Detox/docs/next","mainDocId":"introduction/getting-started","docs":[{"id":"api/actions","path":"/Detox/docs/next/api/actions","sidebar":"apiSidebar"},{"id":"api/copilot","path":"/Detox/docs/next/api/copilot","sidebar":"apiSidebar"},{"id":"api/device","path":"/Detox/docs/next/api/device","sidebar":"apiSidebar"},{"id":"api/expect","path":"/Detox/docs/next/api/expect","sidebar":"apiSidebar"},{"id":"api/internals","path":"/Detox/docs/next/api/internals","sidebar":"apiSidebar"},{"id":"api/logger","path":"/Detox/docs/next/api/logger","sidebar":"apiSidebar"},{"id":"api/matchers","path":"/Detox/docs/next/api/matchers","sidebar":"apiSidebar"},{"id":"api/system","path":"/Detox/docs/next/api/system","sidebar":"apiSidebar"},{"id":"api/webviews","path":"/Detox/docs/next/api/webviews","sidebar":"apiSidebar"},{"id":"articles/design-principles","path":"/Detox/docs/next/articles/design-principles","sidebar":"apiSidebar"},{"id":"articles/how-detox-works","path":"/Detox/docs/next/articles/how-detox-works","sidebar":"apiSidebar"},{"id":"articles/third-party-drivers","path":"/Detox/docs/next/articles/third-party-drivers","sidebar":"apiSidebar"},{"id":"cli/build","path":"/Detox/docs/next/cli/build","sidebar":"apiSidebar"},{"id":"cli/build-framework-cache","path":"/Detox/docs/next/cli/build-framework-cache","sidebar":"apiSidebar"},{"id":"cli/clean-framework-cache","path":"/Detox/docs/next/cli/clean-framework-cache","sidebar":"apiSidebar"},{"id":"cli/init","path":"/Detox/docs/next/cli/init","sidebar":"apiSidebar"},{"id":"cli/overview","path":"/Detox/docs/next/cli/overview","sidebar":"apiSidebar"},{"id":"cli/rebuild-framework-cache","path":"/Detox/docs/next/cli/rebuild-framework-cache","sidebar":"apiSidebar"},{"id":"cli/recorder","path":"/Detox/docs/next/cli/recorder","sidebar":"apiSidebar"},{"id":"cli/reset-lock-file","path":"/Detox/docs/next/cli/reset-lock-file","sidebar":"apiSidebar"},{"id":"cli/run-server","path":"/Detox/docs/next/cli/run-server","sidebar":"apiSidebar"},{"id":"cli/start","path":"/Detox/docs/next/cli/start","sidebar":"apiSidebar"},{"id":"cli/test","path":"/Detox/docs/next/cli/test","sidebar":"apiSidebar"},{"id":"config/apps","path":"/Detox/docs/next/config/apps","sidebar":"apiSidebar"},{"id":"config/artifacts","path":"/Detox/docs/next/config/artifacts","sidebar":"apiSidebar"},{"id":"config/behavior","path":"/Detox/docs/next/config/behavior","sidebar":"apiSidebar"},{"id":"config/devices","path":"/Detox/docs/next/config/devices","sidebar":"apiSidebar"},{"id":"config/logger","path":"/Detox/docs/next/config/logger","sidebar":"apiSidebar"},{"id":"config/overview","path":"/Detox/docs/next/config/overview","sidebar":"apiSidebar"},{"id":"config/session","path":"/Detox/docs/next/config/session","sidebar":"apiSidebar"},{"id":"config/testRunner","path":"/Detox/docs/next/config/testRunner","sidebar":"apiSidebar"},{"id":"contributing","path":"/Detox/docs/next/contributing","sidebar":"contributeSidebar"},{"id":"contributing/code-of-conduct","path":"/Detox/docs/next/contributing/code-of-conduct","sidebar":"contributeSidebar"},{"id":"contributing/code/building-and-testing","path":"/Detox/docs/next/contributing/code/building-and-testing","sidebar":"contributeSidebar"},{"id":"contributing/code/example-projects","path":"/Detox/docs/next/contributing/code/example-projects","sidebar":"contributeSidebar"},{"id":"contributing/code/overview","path":"/Detox/docs/next/contributing/code/overview","sidebar":"contributeSidebar"},{"id":"contributing/code/reviewing-pull-requests","path":"/Detox/docs/next/contributing/code/reviewing-pull-requests","sidebar":"contributeSidebar"},{"id":"contributing/code/setting-up-the-dev-environment","path":"/Detox/docs/next/contributing/code/setting-up-the-dev-environment","sidebar":"contributeSidebar"},{"id":"contributing/code/submitting-pull-requests","path":"/Detox/docs/next/contributing/code/submitting-pull-requests","sidebar":"contributeSidebar"},{"id":"contributing/documentation","path":"/Detox/docs/next/contributing/documentation","sidebar":"contributeSidebar"},{"id":"contributing/feature-requests","path":"/Detox/docs/next/contributing/feature-requests","sidebar":"contributeSidebar"},{"id":"contributing/questions/answering-questions","path":"/Detox/docs/next/contributing/questions/answering-questions","sidebar":"contributeSidebar"},{"id":"contributing/questions/asking-questions","path":"/Detox/docs/next/contributing/questions/asking-questions","sidebar":"contributeSidebar"},{"id":"contributing/reporting-bugs","path":"/Detox/docs/next/contributing/reporting-bugs","sidebar":"contributeSidebar"},{"id":"copilot/best-practices","path":"/Detox/docs/next/copilot/best-practices","sidebar":"tutorialSidebar"},{"id":"copilot/technical-overview","path":"/Detox/docs/next/copilot/technical-overview","sidebar":"tutorialSidebar"},{"id":"copilot/testing-with-copilot","path":"/Detox/docs/next/copilot/testing-with-copilot","sidebar":"tutorialSidebar"},{"id":"demo","path":"/Detox/docs/next/demo"},{"id":"guide/android-dev-env","path":"/Detox/docs/next/guide/android-dev-env","sidebar":"tutorialSidebar"},{"id":"guide/cucumber-js-integration","path":"/Detox/docs/next/guide/cucumber-js-integration","sidebar":"tutorialSidebar"},{"id":"guide/developing-while-writing-tests","path":"/Detox/docs/next/guide/developing-while-writing-tests","sidebar":"tutorialSidebar"},{"id":"guide/genymotion-saas","path":"/Detox/docs/next/guide/genymotion-saas","sidebar":"tutorialSidebar"},{"id":"guide/investigating-test-failure","path":"/Detox/docs/next/guide/investigating-test-failure","sidebar":"tutorialSidebar"},{"id":"guide/launch-args","path":"/Detox/docs/next/guide/launch-args","sidebar":"tutorialSidebar"},{"id":"guide/migration","path":"/Detox/docs/next/guide/migration","sidebar":"tutorialSidebar"},{"id":"guide/mocking","path":"/Detox/docs/next/guide/mocking","sidebar":"tutorialSidebar"},{"id":"guide/mocking-open-with-url","path":"/Detox/docs/next/guide/mocking-open-with-url","sidebar":"tutorialSidebar"},{"id":"guide/mocking-user-activity","path":"/Detox/docs/next/guide/mocking-user-activity","sidebar":"tutorialSidebar"},{"id":"guide/mocking-user-notifications","path":"/Detox/docs/next/guide/mocking-user-notifications","sidebar":"tutorialSidebar"},{"id":"guide/parallel-test-execution","path":"/Detox/docs/next/guide/parallel-test-execution","sidebar":"tutorialSidebar"},{"id":"guide/proguard-configuration","path":"/Detox/docs/next/guide/proguard-configuration","sidebar":"tutorialSidebar"},{"id":"guide/taking-screenshots","path":"/Detox/docs/next/guide/taking-screenshots","sidebar":"tutorialSidebar"},{"id":"guide/test-id","path":"/Detox/docs/next/guide/test-id","sidebar":"tutorialSidebar"},{"id":"guide/testing-webviews","path":"/Detox/docs/next/guide/testing-webviews","sidebar":"tutorialSidebar"},{"id":"guide/typescript","path":"/Detox/docs/next/guide/typescript","sidebar":"tutorialSidebar"},{"id":"guide/uninstalling","path":"/Detox/docs/next/guide/uninstalling","sidebar":"tutorialSidebar"},{"id":"introduction/debugging","path":"/Detox/docs/next/introduction/debugging","sidebar":"tutorialSidebar"},{"id":"introduction/environment-setup","path":"/Detox/docs/next/introduction/environment-setup","sidebar":"tutorialSidebar"},{"id":"introduction/getting-started","path":"/Detox/docs/next/introduction/getting-started","sidebar":"tutorialSidebar"},{"id":"introduction/preparing-for-ci","path":"/Detox/docs/next/introduction/preparing-for-ci","sidebar":"tutorialSidebar"},{"id":"introduction/project-setup","path":"/Detox/docs/next/introduction/project-setup","sidebar":"tutorialSidebar"},{"id":"introduction/your-first-test","path":"/Detox/docs/next/introduction/your-first-test","sidebar":"tutorialSidebar"},{"id":"troubleshooting/artifacts","path":"/Detox/docs/next/troubleshooting/artifacts"},{"id":"troubleshooting/building-the-app","path":"/Detox/docs/next/troubleshooting/building-the-app","sidebar":"tutorialSidebar"},{"id":"troubleshooting/flakiness","path":"/Detox/docs/next/troubleshooting/flakiness","sidebar":"tutorialSidebar"},{"id":"troubleshooting/running-tests","path":"/Detox/docs/next/troubleshooting/running-tests","sidebar":"tutorialSidebar"},{"id":"troubleshooting/synchronization","path":"/Detox/docs/next/troubleshooting/synchronization","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/Detox/docs/next/introduction/getting-started","label":"introduction/getting-started"}},"apiSidebar":{"link":{"path":"/Detox/docs/next/config/overview","label":"config/overview"}},"contributeSidebar":{"link":{"path":"/Detox/docs/next/contributing","label":"contributing"}}}},{"name":"20.x","label":"20.x","isLast":true,"path":"/Detox/docs","mainDocId":"introduction/getting-started","docs":[{"id":"api/actions","path":"/Detox/docs/api/actions","sidebar":"apiSidebar"},{"id":"api/device","path":"/Detox/docs/api/device","sidebar":"apiSidebar"},{"id":"api/expect","path":"/Detox/docs/api/expect","sidebar":"apiSidebar"},{"id":"api/internals","path":"/Detox/docs/api/internals","sidebar":"apiSidebar"},{"id":"api/logger","path":"/Detox/docs/api/logger","sidebar":"apiSidebar"},{"id":"api/matchers","path":"/Detox/docs/api/matchers","sidebar":"apiSidebar"},{"id":"api/system","path":"/Detox/docs/api/system","sidebar":"apiSidebar"},{"id":"api/webviews","path":"/Detox/docs/api/webviews","sidebar":"apiSidebar"},{"id":"articles/design-principles","path":"/Detox/docs/articles/design-principles","sidebar":"apiSidebar"},{"id":"articles/how-detox-works","path":"/Detox/docs/articles/how-detox-works","sidebar":"apiSidebar"},{"id":"articles/third-party-drivers","path":"/Detox/docs/articles/third-party-drivers","sidebar":"apiSidebar"},{"id":"cli/build","path":"/Detox/docs/cli/build","sidebar":"apiSidebar"},{"id":"cli/build-framework-cache","path":"/Detox/docs/cli/build-framework-cache","sidebar":"apiSidebar"},{"id":"cli/clean-framework-cache","path":"/Detox/docs/cli/clean-framework-cache","sidebar":"apiSidebar"},{"id":"cli/init","path":"/Detox/docs/cli/init","sidebar":"apiSidebar"},{"id":"cli/overview","path":"/Detox/docs/cli/overview","sidebar":"apiSidebar"},{"id":"cli/rebuild-framework-cache","path":"/Detox/docs/cli/rebuild-framework-cache","sidebar":"apiSidebar"},{"id":"cli/recorder","path":"/Detox/docs/cli/recorder","sidebar":"apiSidebar"},{"id":"cli/reset-lock-file","path":"/Detox/docs/cli/reset-lock-file","sidebar":"apiSidebar"},{"id":"cli/run-server","path":"/Detox/docs/cli/run-server","sidebar":"apiSidebar"},{"id":"cli/start","path":"/Detox/docs/cli/start","sidebar":"apiSidebar"},{"id":"cli/test","path":"/Detox/docs/cli/test","sidebar":"apiSidebar"},{"id":"config/apps","path":"/Detox/docs/config/apps","sidebar":"apiSidebar"},{"id":"config/artifacts","path":"/Detox/docs/config/artifacts","sidebar":"apiSidebar"},{"id":"config/behavior","path":"/Detox/docs/config/behavior","sidebar":"apiSidebar"},{"id":"config/devices","path":"/Detox/docs/config/devices","sidebar":"apiSidebar"},{"id":"config/logger","path":"/Detox/docs/config/logger","sidebar":"apiSidebar"},{"id":"config/overview","path":"/Detox/docs/config/overview","sidebar":"apiSidebar"},{"id":"config/session","path":"/Detox/docs/config/session","sidebar":"apiSidebar"},{"id":"config/testRunner","path":"/Detox/docs/config/testRunner","sidebar":"apiSidebar"},{"id":"contributing","path":"/Detox/docs/contributing","sidebar":"contributeSidebar"},{"id":"contributing/code-of-conduct","path":"/Detox/docs/contributing/code-of-conduct","sidebar":"contributeSidebar"},{"id":"contributing/code/building-and-testing","path":"/Detox/docs/contributing/code/building-and-testing","sidebar":"contributeSidebar"},{"id":"contributing/code/example-projects","path":"/Detox/docs/contributing/code/example-projects","sidebar":"contributeSidebar"},{"id":"contributing/code/overview","path":"/Detox/docs/contributing/code/overview","sidebar":"contributeSidebar"},{"id":"contributing/code/reviewing-pull-requests","path":"/Detox/docs/contributing/code/reviewing-pull-requests","sidebar":"contributeSidebar"},{"id":"contributing/code/setting-up-the-dev-environment","path":"/Detox/docs/contributing/code/setting-up-the-dev-environment","sidebar":"contributeSidebar"},{"id":"contributing/code/submitting-pull-requests","path":"/Detox/docs/contributing/code/submitting-pull-requests","sidebar":"contributeSidebar"},{"id":"contributing/documentation","path":"/Detox/docs/contributing/documentation","sidebar":"contributeSidebar"},{"id":"contributing/feature-requests","path":"/Detox/docs/contributing/feature-requests","sidebar":"contributeSidebar"},{"id":"contributing/questions/answering-questions","path":"/Detox/docs/contributing/questions/answering-questions","sidebar":"contributeSidebar"},{"id":"contributing/questions/asking-questions","path":"/Detox/docs/contributing/questions/asking-questions","sidebar":"contributeSidebar"},{"id":"contributing/reporting-bugs","path":"/Detox/docs/contributing/reporting-bugs","sidebar":"contributeSidebar"},{"id":"demo","path":"/Detox/docs/demo"},{"id":"guide/android-dev-env","path":"/Detox/docs/guide/android-dev-env","sidebar":"tutorialSidebar"},{"id":"guide/cucumber-js-integration","path":"/Detox/docs/guide/cucumber-js-integration","sidebar":"tutorialSidebar"},{"id":"guide/developing-while-writing-tests","path":"/Detox/docs/guide/developing-while-writing-tests","sidebar":"tutorialSidebar"},{"id":"guide/genymotion-saas","path":"/Detox/docs/guide/genymotion-saas","sidebar":"tutorialSidebar"},{"id":"guide/investigating-test-failure","path":"/Detox/docs/guide/investigating-test-failure","sidebar":"tutorialSidebar"},{"id":"guide/launch-args","path":"/Detox/docs/guide/launch-args","sidebar":"tutorialSidebar"},{"id":"guide/migration","path":"/Detox/docs/guide/migration","sidebar":"tutorialSidebar"},{"id":"guide/mocking","path":"/Detox/docs/guide/mocking","sidebar":"tutorialSidebar"},{"id":"guide/mocking-open-with-url","path":"/Detox/docs/guide/mocking-open-with-url","sidebar":"tutorialSidebar"},{"id":"guide/mocking-user-activity","path":"/Detox/docs/guide/mocking-user-activity","sidebar":"tutorialSidebar"},{"id":"guide/mocking-user-notifications","path":"/Detox/docs/guide/mocking-user-notifications","sidebar":"tutorialSidebar"},{"id":"guide/parallel-test-execution","path":"/Detox/docs/guide/parallel-test-execution","sidebar":"tutorialSidebar"},{"id":"guide/proguard-configuration","path":"/Detox/docs/guide/proguard-configuration","sidebar":"tutorialSidebar"},{"id":"guide/taking-screenshots","path":"/Detox/docs/guide/taking-screenshots","sidebar":"tutorialSidebar"},{"id":"guide/test-id","path":"/Detox/docs/guide/test-id","sidebar":"tutorialSidebar"},{"id":"guide/testing-webviews","path":"/Detox/docs/guide/testing-webviews","sidebar":"tutorialSidebar"},{"id":"guide/typescript","path":"/Detox/docs/guide/typescript","sidebar":"tutorialSidebar"},{"id":"guide/uninstalling","path":"/Detox/docs/guide/uninstalling","sidebar":"tutorialSidebar"},{"id":"introduction/debugging","path":"/Detox/docs/introduction/debugging","sidebar":"tutorialSidebar"},{"id":"introduction/environment-setup","path":"/Detox/docs/introduction/environment-setup","sidebar":"tutorialSidebar"},{"id":"introduction/getting-started","path":"/Detox/docs/introduction/getting-started","sidebar":"tutorialSidebar"},{"id":"introduction/preparing-for-ci","path":"/Detox/docs/introduction/preparing-for-ci","sidebar":"tutorialSidebar"},{"id":"introduction/project-setup","path":"/Detox/docs/introduction/project-setup","sidebar":"tutorialSidebar"},{"id":"introduction/your-first-test","path":"/Detox/docs/introduction/your-first-test","sidebar":"tutorialSidebar"},{"id":"troubleshooting/artifacts","path":"/Detox/docs/troubleshooting/artifacts"},{"id":"troubleshooting/building-the-app","path":"/Detox/docs/troubleshooting/building-the-app","sidebar":"tutorialSidebar"},{"id":"troubleshooting/flakiness","path":"/Detox/docs/troubleshooting/flakiness","sidebar":"tutorialSidebar"},{"id":"troubleshooting/running-tests","path":"/Detox/docs/troubleshooting/running-tests","sidebar":"tutorialSidebar"},{"id":"troubleshooting/synchronization","path":"/Detox/docs/troubleshooting/synchronization","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/Detox/docs/introduction/getting-started","label":"introduction/getting-started"}},"apiSidebar":{"link":{"path":"/Detox/docs/config/overview","label":"config/overview"}},"contributeSidebar":{"link":{"path":"/Detox/docs/contributing","label":"contributing"}}}},{"name":"19.x","label":"19.x","isLast":false,"path":"/Detox/docs/19.x","mainDocId":"README","docs":[{"id":"actions-on-element","path":"/Detox/docs/19.x/api/actions-on-element","sidebar":"tutorialSidebar"},{"id":"android","path":"/Detox/docs/19.x/introduction/android","sidebar":"tutorialSidebar"},{"id":"android-dev-env","path":"/Detox/docs/19.x/introduction/android-dev-env","sidebar":"tutorialSidebar"},{"id":"artifacts","path":"/Detox/docs/19.x/api/artifacts","sidebar":"tutorialSidebar"},{"id":"building-the-app","path":"/Detox/docs/19.x/troubleshooting/building-the-app","sidebar":"tutorialSidebar"},{"id":"config/overview","path":"/Detox/docs/19.x/config/overview","sidebar":"tutorialSidebar"},{"id":"contributing","path":"/Detox/docs/19.x/contributing","sidebar":"tutorialSidebar"},{"id":"debugging-in-android-studio","path":"/Detox/docs/19.x/guide/debugging-in-android-studio","sidebar":"tutorialSidebar"},{"id":"debugging-in-xcode","path":"/Detox/docs/19.x/guide/debugging-in-xcode","sidebar":"tutorialSidebar"},{"id":"design-principles","path":"/Detox/docs/19.x/introduction/design-principles","sidebar":"tutorialSidebar"},{"id":"detox-cli","path":"/Detox/docs/19.x/api/detox-cli","sidebar":"tutorialSidebar"},{"id":"detox-object-api","path":"/Detox/docs/19.x/api/detox-object-api","sidebar":"tutorialSidebar"},{"id":"developing-while-writing-tests","path":"/Detox/docs/19.x/guide/developing-while-writing-tests","sidebar":"tutorialSidebar"},{"id":"device-object-api","path":"/Detox/docs/19.x/api/device-object-api","sidebar":"tutorialSidebar"},{"id":"expect","path":"/Detox/docs/19.x/api/expect","sidebar":"tutorialSidebar"},{"id":"expo","path":"/Detox/docs/19.x/guide/expo","sidebar":"tutorialSidebar"},{"id":"flakiness","path":"/Detox/docs/19.x/troubleshooting/flakiness","sidebar":"tutorialSidebar"},{"id":"how-detox-works","path":"/Detox/docs/19.x/introduction/how-detox-works","sidebar":"tutorialSidebar"},{"id":"introduction/getting-started","path":"/Detox/docs/19.x/introduction/getting-started","sidebar":"tutorialSidebar"},{"id":"ios","path":"/Detox/docs/19.x/introduction/ios","sidebar":"tutorialSidebar"},{"id":"ios-dev-env","path":"/Detox/docs/19.x/introduction/ios-dev-env","sidebar":"tutorialSidebar"},{"id":"jest","path":"/Detox/docs/19.x/guide/jest","sidebar":"tutorialSidebar"},{"id":"launch-args","path":"/Detox/docs/19.x/api/launch-args","sidebar":"tutorialSidebar"},{"id":"matchers","path":"/Detox/docs/19.x/api/matchers","sidebar":"tutorialSidebar"},{"id":"migration","path":"/Detox/docs/19.x/guide/migration","sidebar":"tutorialSidebar"},{"id":"mocha","path":"/Detox/docs/19.x/guide/mocha","sidebar":"tutorialSidebar"},{"id":"mocking","path":"/Detox/docs/19.x/guide/mocking","sidebar":"tutorialSidebar"},{"id":"mocking-open-with-url","path":"/Detox/docs/19.x/api/mocking-open-with-url","sidebar":"tutorialSidebar"},{"id":"mocking-user-activity","path":"/Detox/docs/19.x/api/mocking-user-activity","sidebar":"tutorialSidebar"},{"id":"mocking-user-notifications","path":"/Detox/docs/19.x/api/mocking-user-notifications","sidebar":"tutorialSidebar"},{"id":"parallel-test-execution","path":"/Detox/docs/19.x/guide/parallel-test-execution","sidebar":"tutorialSidebar"},{"id":"README","path":"/Detox/docs/19.x/"},{"id":"running-locally","path":"/Detox/docs/19.x/guide/running-locally","sidebar":"tutorialSidebar"},{"id":"running-on-ci","path":"/Detox/docs/19.x/guide/running-on-ci","sidebar":"tutorialSidebar"},{"id":"running-tests","path":"/Detox/docs/19.x/troubleshooting/running-tests","sidebar":"tutorialSidebar"},{"id":"screenshots","path":"/Detox/docs/19.x/api/screenshots","sidebar":"tutorialSidebar"},{"id":"synchronization","path":"/Detox/docs/19.x/troubleshooting/synchronization","sidebar":"tutorialSidebar"},{"id":"test-lifecycle","path":"/Detox/docs/19.x/api/test-lifecycle","sidebar":"tutorialSidebar"},{"id":"third-party-drivers","path":"/Detox/docs/19.x/guide/third-party-drivers","sidebar":"tutorialSidebar"},{"id":"troubleshooting","path":"/Detox/docs/19.x/troubleshooting/troubleshooting","sidebar":"tutorialSidebar"},{"id":"uninstalling","path":"/Detox/docs/19.x/guide/uninstalling","sidebar":"tutorialSidebar"},{"id":"workflows","path":"/Detox/docs/19.x/introduction/workflows","sidebar":"tutorialSidebar"},{"id":"writing-first-test","path":"/Detox/docs/19.x/introduction/writing-first-test","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/Detox/docs/19.x/introduction/getting-started","label":"introduction/getting-started"}}}}],"breadcrumbs":true}}}'),a=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var c=n(22654);const s=JSON.parse('{"docusaurusVersion":"2.4.3","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"2.4.3"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"2.4.3"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"2.4.3"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"2.4.3"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"2.4.3"},"docusaurus-theme-search-algolia":{"type":"package","name":"@docusaurus/theme-search-algolia","version":"2.4.3"},"docusaurus-plugin-sass":{"type":"package","name":"docusaurus-plugin-sass","version":"0.2.5"}}}'),l={siteConfig:o.default,siteMetadata:s,globalData:i,i18n:a,codeTranslations:c},u=r.createContext(l);function d(e){let{children:t}=e;return r.createElement(u.Provider,{value:l},t)}},67489:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});var r=n(96540),o=n(38193),i=n(5260),a=n(70440),c=n(97173);function s(e){let{error:t,tryAgain:n}=e;return r.createElement("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"}},r.createElement("h1",{style:{fontSize:"3rem"}},"This page crashed"),r.createElement("button",{type:"button",onClick:n,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"}},"Try again"),r.createElement(l,{error:t}))}function l(e){let{error:t}=e;const n=(0,a.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return r.createElement("p",{style:{whiteSpace:"pre-wrap"}},n)}function u(e){let{error:t,tryAgain:n}=e;return r.createElement(f,{fallback:()=>r.createElement(s,{error:t,tryAgain:n})},r.createElement(i.A,null,r.createElement("title",null,"Page Error")),r.createElement(c.A,null,r.createElement(s,{error:t,tryAgain:n})))}const d=e=>r.createElement(u,e);class f extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){o.A.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??d)(e)}return e??null}}},38193:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,o={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},5260:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(96540),o=n(80545);function i(e){return r.createElement(o.mg,e)}},75489:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(58168),o=n(96540),i=n(54625),a=n(70440),c=n(44586),s=n(16654),l=n(38193);const u=o.createContext({collectLink:()=>{}});var d=n(86025);function f(e,t){let{isNavLink:n,to:f,href:p,activeClassName:h,isActive:m,"data-noBrokenLinkCheck":g,autoAddBaseUrl:b=!0,...v}=e;const{siteConfig:{trailingSlash:y,baseUrl:x}}=(0,c.A)(),{withBaseUrl:w}=(0,d.h)(),S=(0,o.useContext)(u),_=(0,o.useRef)(null);(0,o.useImperativeHandle)(t,(()=>_.current));const E=f||p;const k=(0,s.A)(E),D=E?.replace("pathname://","");let A=void 0!==D?(O=D,b&&(e=>e.startsWith("/"))(O)?w(O):O):void 0;var O;A&&k&&(A=(0,a.applyTrailingSlash)(A,{trailingSlash:y,baseUrl:x}));const C=(0,o.useRef)(!1),T=n?i.k2:i.N_,P=l.A.canUseIntersectionObserver,I=(0,o.useRef)(),R=()=>{C.current||null==A||(window.docusaurus.preload(A),C.current=!0)};(0,o.useEffect)((()=>(!P&&k&&null!=A&&window.docusaurus.prefetch(A),()=>{P&&I.current&&I.current.disconnect()})),[I,A,P,k]);const N=A?.startsWith("#")??!1,j=!A||!k||N;return j||g||S.collectLink(A),j?o.createElement("a",(0,r.A)({ref:_,href:A},E&&!k&&{target:"_blank",rel:"noopener noreferrer"},v)):o.createElement(T,(0,r.A)({},v,{onMouseEnter:R,onTouchStart:R,innerRef:e=>{_.current=e,P&&e&&k&&(I.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(I.current.unobserve(e),I.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))}))})),I.current.observe(e))},to:A},n&&{isActive:m,activeClassName:h}))}const p=o.forwardRef(f)},21312:(e,t,n)=>{"use strict";n.d(t,{A:()=>s,T:()=>c});var r=n(96540);function o(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var i=n(22654);function a(e){let{id:t,message:n}=e;if(void 0===t&&void 0===n)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[t??n]??n??t}function c(e,t){let{message:n,id:r}=e;return o(a({message:n,id:r}),t)}function s(e){let{children:t,id:n,values:i}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal children",t),new Error("The Docusaurus component only accept simple string values");const c=a({message:t,id:n});return r.createElement(r.Fragment,null,o(c,i))}},17065:(e,t,n)=>{"use strict";n.d(t,{W:()=>r});const r="default"},16654:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function o(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>o,z:()=>r})},86025:(e,t,n)=>{"use strict";n.d(t,{A:()=>c,h:()=>a});var r=n(96540),o=n(44586),i=n(16654);function a(){const{siteConfig:{baseUrl:e,url:t}}=(0,o.A)(),n=(0,r.useCallback)(((n,r)=>function(e,t,n,r){let{forcePrependBaseUrl:o=!1,absolute:a=!1}=void 0===r?{}:r;if(!n||n.startsWith("#")||(0,i.z)(n))return n;if(o)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const c=n.startsWith(t)?n:t+n.replace(/^\//,"");return a?e+c:c}(t,e,n,r)),[t,e]);return{withBaseUrl:n}}function c(e,t){void 0===t&&(t={});const{withBaseUrl:n}=a();return n(e,t)}},44586:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(96540),o=n(26988);function i(){return(0,r.useContext)(o.o)}},92303:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(96540),o=n(6125);function i(){return(0,r.useContext)(o.o)}},86921:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function o(e){const t={};return function e(n,o){Object.entries(n).forEach((n=>{let[i,a]=n;const c=o?`${o}.${i}`:i;r(a)?e(a,c):t[c]=a}))}(e),t}},53102:(e,t,n)=>{"use strict";n.d(t,{W:()=>i,o:()=>o});var r=n(96540);const o=r.createContext(null);function i(e){let{children:t,value:n}=e;const i=r.useContext(o),a=(0,r.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const r={...t.data,...n?.data};return{plugin:t.plugin,data:r}}({parent:i,value:n})),[i,n]);return r.createElement(o.Provider,{value:a},t)}},44070:(e,t,n)=>{"use strict";n.d(t,{zK:()=>b,vT:()=>p,gk:()=>h,Gy:()=>d,HW:()=>v,ht:()=>f,r7:()=>g,jh:()=>m});var r=n(56347),o=n(44586),i=n(17065);function a(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,o.A)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const c=e=>e.versions.find((e=>e.isLast));function s(e,t){const n=c(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})))}function l(e,t){const n=s(e,t),o=n?.docs.find((e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:o,alternateDocVersions:o?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(o.id):{}}}const u={},d=()=>a("docusaurus-plugin-content-docs")??u,f=e=>function(e,t,n){void 0===t&&(t=i.W),void 0===n&&(n={});const r=a(e),o=r?.[t];if(!o&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return o}("docusaurus-plugin-content-docs",e,{failfast:!0});function p(e){void 0===e&&(e={});const t=d(),{pathname:n}=(0,r.zy)();return function(e,t,n){void 0===n&&(n={});const o=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,r.B6)(t,{path:n.path,exact:!1,strict:!1})})),i=o?{pluginId:o[0],pluginData:o[1]}:void 0;if(!i&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return i}(t,n,e)}function h(e){void 0===e&&(e={});const t=p(e),{pathname:n}=(0,r.zy)();if(!t)return;return{activePlugin:t,activeVersion:s(t.pluginData,n)}}function m(e){return f(e).versions}function g(e){const t=f(e);return c(t)}function b(e){const t=f(e),{pathname:n}=(0,r.zy)();return l(t,n)}function v(e){const t=f(e),{pathname:n}=(0,r.zy)();return function(e,t){const n=c(e);return{latestDocSuggestion:l(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},76294:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(5947),o=n.n(r);o().configure({showSpinner:!1});const i={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{o().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){o().done()}}},26134:(e,t,n)=>{"use strict";var r=n(61258),o=n(4784);!function(e){const{themeConfig:{prism:t}}=o.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{n(20867)(`./prism-${e}`)})),delete globalThis.Prism}(r.A)},43186:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(96540);const o={iconExternalLink:"iconExternalLink_nPIU"};function i(e){let{width:t=13.5,height:n=13.5}=e;return r.createElement("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:o.iconExternalLink},r.createElement("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"}))}},97173:(e,t,n)=>{"use strict";n.d(t,{A:()=>wt});var r=n(96540),o=n(20053),i=n(67489),a=n(69024),c=n(58168),s=n(56347),l=n(21312),u=n(75062);const d="__docusaurus_skipToContent_fallback";function f(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function p(){const e=(0,r.useRef)(null),{action:t}=(0,s.W6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&f(t)}),[]);return(0,u.$)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&f(e.current)})),{containerRef:e,onClick:n}}const h=(0,l.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function m(e){const t=e.children??h,{containerRef:n,onClick:o}=p();return r.createElement("div",{ref:n,role:"region","aria-label":h},r.createElement("a",(0,c.A)({},e,{href:`#${d}`,onClick:o}),t))}var g=n(17559),b=n(14090);const v={skipToContent:"skipToContent_fXgn"};function y(){return r.createElement(m,{className:v.skipToContent})}var x=n(6342),w=n(65041);function S(e){let{width:t=21,height:n=21,color:o="currentColor",strokeWidth:i=1.2,className:a,...s}=e;return r.createElement("svg",(0,c.A)({viewBox:"0 0 15 15",width:t,height:n},s),r.createElement("g",{stroke:o,strokeWidth:i},r.createElement("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})))}const _={closeButton:"closeButton_CVFx"};function E(e){return r.createElement("button",(0,c.A)({type:"button","aria-label":(0,l.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"})},e,{className:(0,o.A)("clean-btn close",_.closeButton,e.className)}),r.createElement(S,{width:14,height:14,strokeWidth:3.1}))}const k={content:"content_knG7"};function D(e){const{announcementBar:t}=(0,x.p)(),{content:n}=t;return r.createElement("div",(0,c.A)({},e,{className:(0,o.A)(k.content,e.className),dangerouslySetInnerHTML:{__html:n}}))}const A={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function O(){const{announcementBar:e}=(0,x.p)(),{isActive:t,close:n}=(0,w.Mj)();if(!t)return null;const{backgroundColor:o,textColor:i,isCloseable:a}=e;return r.createElement("div",{className:A.announcementBar,style:{backgroundColor:o,color:i},role:"banner"},a&&r.createElement("div",{className:A.announcementBarPlaceholder}),r.createElement(D,{className:A.announcementBarContent}),a&&r.createElement(E,{onClick:n,className:A.announcementBarClose}))}var C=n(22069),T=n(23104);var P=n(89532),I=n(75600);const R=r.createContext(null);function N(e){let{children:t}=e;const n=function(){const e=(0,C.M)(),t=(0,I.YL)(),[n,o]=(0,r.useState)(!1),i=null!==t.component,a=(0,P.ZC)(i);return(0,r.useEffect)((()=>{i&&!a&&o(!0)}),[i,a]),(0,r.useEffect)((()=>{i?e.shown||o(!0):o(!1)}),[e.shown,i]),(0,r.useMemo)((()=>[n,o]),[n])}();return r.createElement(R.Provider,{value:n},t)}function j(e){if(e.component){const t=e.component;return r.createElement(t,e.props)}}function L(){const e=(0,r.useContext)(R);if(!e)throw new P.dV("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,o=(0,r.useCallback)((()=>n(!1)),[n]),i=(0,I.YL)();return(0,r.useMemo)((()=>({shown:t,hide:o,content:j(i)})),[o,i,t])}function M(e){let{header:t,primaryMenu:n,secondaryMenu:i}=e;const{shown:a}=L();return r.createElement("div",{className:"navbar-sidebar"},t,r.createElement("div",{className:(0,o.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":a})},r.createElement("div",{className:"navbar-sidebar__item menu"},n),r.createElement("div",{className:"navbar-sidebar__item menu"},i)))}var B=n(95293),F=n(92303);function z(e){return r.createElement("svg",(0,c.A)({viewBox:"0 0 24 24",width:24,height:24},e),r.createElement("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"}))}function U(e){return r.createElement("svg",(0,c.A)({viewBox:"0 0 24 24",width:24,height:24},e),r.createElement("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"}))}const $={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function q(e){let{className:t,buttonClassName:n,value:i,onChange:a}=e;const c=(0,F.A)(),s=(0,l.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===i?(0,l.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,l.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return r.createElement("div",{className:(0,o.A)($.toggle,t)},r.createElement("button",{className:(0,o.A)("clean-btn",$.toggleButton,!c&&$.toggleButtonDisabled,n),type:"button",onClick:()=>a("dark"===i?"light":"dark"),disabled:!c,title:s,"aria-label":s,"aria-live":"polite"},r.createElement(z,{className:(0,o.A)($.toggleIcon,$.lightToggleIcon)}),r.createElement(U,{className:(0,o.A)($.toggleIcon,$.darkToggleIcon)})))}const H=r.memo(q),V={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function W(e){let{className:t}=e;const n=(0,x.p)().navbar.style,o=(0,x.p)().colorMode.disableSwitch,{colorMode:i,setColorMode:a}=(0,B.G)();return o?null:r.createElement(H,{className:t,buttonClassName:"dark"===n?V.darkNavbarColorModeToggle:void 0,value:i,onChange:a})}var G=n(23465);function K(){return r.createElement(G.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function Y(){const e=(0,C.M)();return r.createElement("button",{type:"button","aria-label":(0,l.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle()},r.createElement(S,{color:"var(--ifm-color-emphasis-600)"}))}function Q(){return r.createElement("div",{className:"navbar-sidebar__brand"},r.createElement(K,null),r.createElement(W,{className:"margin-right--md"}),r.createElement(Y,null))}var Z=n(75489),X=n(86025),J=n(16654),ee=n(91252),te=n(43186);function ne(e){let{activeBasePath:t,activeBaseRegex:n,to:o,href:i,label:a,html:s,isDropdownLink:l,prependBaseUrlToHref:u,...d}=e;const f=(0,X.A)(o),p=(0,X.A)(t),h=(0,X.A)(i,{forcePrependBaseUrl:!0}),m=a&&i&&!(0,J.A)(i),g=s?{dangerouslySetInnerHTML:{__html:s}}:{children:r.createElement(r.Fragment,null,a,m&&r.createElement(te.A,l&&{width:12,height:12}))};return i?r.createElement(Z.A,(0,c.A)({href:u?h:i},d,g)):r.createElement(Z.A,(0,c.A)({to:f,isNavLink:!0},(t||n)&&{isActive:(e,t)=>n?(0,ee.G)(n,t.pathname):t.pathname.startsWith(p)},d,g))}function re(e){let{className:t,isDropdownItem:n=!1,...i}=e;const a=r.createElement(ne,(0,c.A)({className:(0,o.A)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n},i));return n?r.createElement("li",null,a):a}function oe(e){let{className:t,isDropdownItem:n,...i}=e;return r.createElement("li",{className:"menu__list-item"},r.createElement(ne,(0,c.A)({className:(0,o.A)("menu__link",t)},i)))}function ie(e){let{mobile:t=!1,position:n,...o}=e;const i=t?oe:re;return r.createElement(i,(0,c.A)({},o,{activeClassName:o.activeClassName??(t?"menu__link--active":"navbar__link--active")}))}var ae=n(41422),ce=n(99169),se=n(44586);function le(e,t){return e.some((e=>function(e,t){return!!(0,ce.ys)(e.to,t)||!!(0,ee.G)(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function ue(e){let{items:t,position:n,className:i,onClick:a,...s}=e;const l=(0,r.useRef)(null),[u,d]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{l.current&&!l.current.contains(e.target)&&d(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[l]),r.createElement("div",{ref:l,className:(0,o.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":u})},r.createElement(ne,(0,c.A)({"aria-haspopup":"true","aria-expanded":u,role:"button",href:s.to?void 0:"#",className:(0,o.A)("navbar__link",i)},s,{onClick:s.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),d(!u))}}),s.children??s.label),r.createElement("ul",{className:"dropdown__menu"},t.map(((e,t)=>r.createElement(Me,(0,c.A)({isDropdownItem:!0,activeClassName:"dropdown__link--active"},e,{key:t}))))))}function de(e){let{items:t,className:n,position:i,onClick:a,...l}=e;const u=function(){const{siteConfig:{baseUrl:e}}=(0,se.A)(),{pathname:t}=(0,s.zy)();return t.replace(e,"/")}(),d=le(t,u),{collapsed:f,toggleCollapsed:p,setCollapsed:h}=(0,ae.u)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&h(!d)}),[u,d,h]),r.createElement("li",{className:(0,o.A)("menu__list-item",{"menu__list-item--collapsed":f})},r.createElement(ne,(0,c.A)({role:"button",className:(0,o.A)("menu__link menu__link--sublist menu__link--sublist-caret",n)},l,{onClick:e=>{e.preventDefault(),p()}}),l.children??l.label),r.createElement(ae.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:f},t.map(((e,t)=>r.createElement(Me,(0,c.A)({mobile:!0,isDropdownItem:!0,onClick:a,activeClassName:"menu__link--active"},e,{key:t}))))))}function fe(e){let{mobile:t=!1,...n}=e;const o=t?de:ue;return r.createElement(o,n)}var pe=n(32131);function he(e){let{width:t=20,height:n=20,...o}=e;return r.createElement("svg",(0,c.A)({viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0},o),r.createElement("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"}))}const me="iconLanguage_nlXk";var ge=n(55600),be=n(5260),ve=n(24255),ye=n(51062),xe=n(2967);var we=n(40961);const Se={button:{buttonText:(0,l.T)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"}),buttonAriaLabel:(0,l.T)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"})},modal:{searchBox:{resetButtonTitle:(0,l.T)({id:"theme.SearchModal.searchBox.resetButtonTitle",message:"Clear the query",description:"The label and ARIA label for search box reset button"}),resetButtonAriaLabel:(0,l.T)({id:"theme.SearchModal.searchBox.resetButtonTitle",message:"Clear the query",description:"The label and ARIA label for search box reset button"}),cancelButtonText:(0,l.T)({id:"theme.SearchModal.searchBox.cancelButtonText",message:"Cancel",description:"The label and ARIA label for search box cancel button"}),cancelButtonAriaLabel:(0,l.T)({id:"theme.SearchModal.searchBox.cancelButtonText",message:"Cancel",description:"The label and ARIA label for search box cancel button"})},startScreen:{recentSearchesTitle:(0,l.T)({id:"theme.SearchModal.startScreen.recentSearchesTitle",message:"Recent",description:"The title for recent searches"}),noRecentSearchesText:(0,l.T)({id:"theme.SearchModal.startScreen.noRecentSearchesText",message:"No recent searches",description:"The text when no recent searches"}),saveRecentSearchButtonTitle:(0,l.T)({id:"theme.SearchModal.startScreen.saveRecentSearchButtonTitle",message:"Save this search",description:"The label for save recent search button"}),removeRecentSearchButtonTitle:(0,l.T)({id:"theme.SearchModal.startScreen.removeRecentSearchButtonTitle",message:"Remove this search from history",description:"The label for remove recent search button"}),favoriteSearchesTitle:(0,l.T)({id:"theme.SearchModal.startScreen.favoriteSearchesTitle",message:"Favorite",description:"The title for favorite searches"}),removeFavoriteSearchButtonTitle:(0,l.T)({id:"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle",message:"Remove this search from favorites",description:"The label for remove favorite search button"})},errorScreen:{titleText:(0,l.T)({id:"theme.SearchModal.errorScreen.titleText",message:"Unable to fetch results",description:"The title for error screen of search modal"}),helpText:(0,l.T)({id:"theme.SearchModal.errorScreen.helpText",message:"You might want to check your network connection.",description:"The help text for error screen of search modal"})},footer:{selectText:(0,l.T)({id:"theme.SearchModal.footer.selectText",message:"to select",description:"The explanatory text of the action for the enter key"}),selectKeyAriaLabel:(0,l.T)({id:"theme.SearchModal.footer.selectKeyAriaLabel",message:"Enter key",description:"The ARIA label for the Enter key button that makes the selection"}),navigateText:(0,l.T)({id:"theme.SearchModal.footer.navigateText",message:"to navigate",description:"The explanatory text of the action for the Arrow up and Arrow down key"}),navigateUpKeyAriaLabel:(0,l.T)({id:"theme.SearchModal.footer.navigateUpKeyAriaLabel",message:"Arrow up",description:"The ARIA label for the Arrow up key button that makes the navigation"}),navigateDownKeyAriaLabel:(0,l.T)({id:"theme.SearchModal.footer.navigateDownKeyAriaLabel",message:"Arrow down",description:"The ARIA label for the Arrow down key button that makes the navigation"}),closeText:(0,l.T)({id:"theme.SearchModal.footer.closeText",message:"to close",description:"The explanatory text of the action for Escape key"}),closeKeyAriaLabel:(0,l.T)({id:"theme.SearchModal.footer.closeKeyAriaLabel",message:"Escape key",description:"The ARIA label for the Escape key button that close the modal"}),searchByText:(0,l.T)({id:"theme.SearchModal.footer.searchByText",message:"Search by",description:"The text explain that the search is making by Algolia"})},noResultsScreen:{noResultsText:(0,l.T)({id:"theme.SearchModal.noResultsScreen.noResultsText",message:"No results for",description:"The text explains that there are no results for the following search"}),suggestedQueryText:(0,l.T)({id:"theme.SearchModal.noResultsScreen.suggestedQueryText",message:"Try searching for",description:"The text for the suggested query when no results are found for the following search"}),reportMissingResultsText:(0,l.T)({id:"theme.SearchModal.noResultsScreen.reportMissingResultsText",message:"Believe this query should return results?",description:"The text for the question where the user thinks there are missing results"}),reportMissingResultsLinkText:(0,l.T)({id:"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText",message:"Let us know.",description:"The text for the link to report missing results"})}},placeholder:(0,l.T)({id:"theme.SearchModal.placeholder",message:"Search docs",description:"The placeholder of the input of the DocSearch pop-up modal"})};let _e=null;function Ee(e){let{hit:t,children:n}=e;return r.createElement(Z.A,{to:t.url},n)}function ke(e){let{state:t,onClose:n}=e;const o=(0,ve.w)();return r.createElement(Z.A,{to:o(t.query),onClick:n},r.createElement(l.A,{id:"theme.SearchBar.seeAll",values:{count:t.context.nbHits}},"See all {count} results"))}function De(e){let{contextualSearch:t,externalUrlRegex:o,...i}=e;const{siteMetadata:a}=(0,se.A)(),l=(0,ye.C)(),u=function(){const{locale:e,tags:t}=(0,xe.af)();return[`language:${e}`,t.map((e=>`docusaurus_tag:${e}`))]}(),d=i.searchParameters?.facetFilters??[],f=t?function(e,t){const n=e=>"string"==typeof e?[e]:e;return[...n(e),...n(t)]}(u,d):d,p={...i.searchParameters,facetFilters:f},h=(0,s.W6)(),m=(0,r.useRef)(null),g=(0,r.useRef)(null),[b,v]=(0,r.useState)(!1),[y,x]=(0,r.useState)(void 0),w=(0,r.useCallback)((()=>_e?Promise.resolve():Promise.all([n.e(8158).then(n.bind(n,48158)),Promise.all([n.e(1869),n.e(8913)]).then(n.bind(n,58913)),Promise.all([n.e(1869),n.e(416)]).then(n.bind(n,90416))]).then((e=>{let[{DocSearchModal:t}]=e;_e=t}))),[]),S=(0,r.useCallback)((()=>{w().then((()=>{m.current=document.createElement("div"),document.body.insertBefore(m.current,document.body.firstChild),v(!0)}))}),[w,v]),_=(0,r.useCallback)((()=>{v(!1),m.current?.remove()}),[v]),E=(0,r.useCallback)((e=>{w().then((()=>{v(!0),x(e.key)}))}),[w,v,x]),k=(0,r.useRef)({navigate(e){let{itemUrl:t}=e;(0,ee.G)(o,t)?window.location.href=t:h.push(t)}}).current,D=(0,r.useRef)((e=>i.transformItems?i.transformItems(e):e.map((e=>({...e,url:l(e.url)}))))).current,A=(0,r.useMemo)((()=>e=>r.createElement(ke,(0,c.A)({},e,{onClose:_}))),[_]),O=(0,r.useCallback)((e=>(e.addAlgoliaAgent("docusaurus",a.docusaurusVersion),e)),[a.docusaurusVersion]);return(0,ge.E8)({isOpen:b,onOpen:S,onClose:_,onInput:E,searchButtonRef:g}),r.createElement(r.Fragment,null,r.createElement(be.A,null,r.createElement("link",{rel:"preconnect",href:`https://${i.appId}-dsn.algolia.net`,crossOrigin:"anonymous"})),r.createElement(ge.Bc,{onTouchStart:w,onFocus:w,onMouseOver:w,onClick:S,ref:g,translations:Se.button}),b&&_e&&m.current&&(0,we.createPortal)(r.createElement(_e,(0,c.A)({onClose:_,initialScrollY:window.scrollY,initialQuery:y,navigator:k,transformItems:D,hitComponent:Ee,transformSearchClient:O},i.searchPagePath&&{resultsFooterComponent:A},i,{searchParameters:p,placeholder:Se.placeholder,translations:Se.modal})),m.current))}function Ae(){const{siteConfig:e}=(0,se.A)();return r.createElement(De,e.themeConfig.algolia)}const Oe={searchBox:"searchBox_ZlJk"};function Ce(e){let{children:t,className:n}=e;return r.createElement("div",{className:(0,o.A)(n,Oe.searchBox)},t)}const Te={searchWrapper:"searchWrapper_jYdA"};function Pe(e){return r.createElement(r.Fragment,null,r.createElement(Ce,(0,c.A)({className:Te.searchWrapper},e)))}var Ie=n(44070),Re=n(84142);var Ne=n(55597);const je=e=>e.docs.find((t=>t.id===e.mainDocId));const Le={default:ie,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:o,...i}=e;const{i18n:{currentLocale:a,locales:u,localeConfigs:d}}=(0,se.A)(),f=(0,pe.o)(),{search:p,hash:h}=(0,s.zy)(),m=[...n,...u.map((e=>{const n=`${`pathname://${f.createUrl({locale:e,fullyQualified:!1})}`}${p}${h}`;return{label:d[e].label,lang:d[e].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:e===a?t?"menu__link--active":"dropdown__link--active":""}})),...o],g=t?(0,l.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):d[a].label;return r.createElement(fe,(0,c.A)({},i,{mobile:t,label:r.createElement(r.Fragment,null,r.createElement(he,{className:me}),g),items:m}))},search:function(e){let{mobile:t,className:n}=e;return t?null:r.createElement(Pe,{className:n},r.createElement(Ae,null))},dropdown:fe,html:function(e){let{value:t,className:n,mobile:i=!1,isDropdownItem:a=!1}=e;const c=a?"li":"div";return r.createElement(c,{className:(0,o.A)({navbar__item:!i&&!a,"menu__list-item":i},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:o,...i}=e;const{activeDoc:a}=(0,Ie.zK)(o),s=(0,Re.QB)(t,o);return null===s?null:r.createElement(ie,(0,c.A)({exact:!0},i,{isActive:()=>a?.path===s.path||!!a?.sidebar&&a.sidebar===s.sidebar,label:n??s.id,to:s.path}))},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:o,...i}=e;const{activeDoc:a}=(0,Ie.zK)(o),s=(0,Re.fW)(t,o).link;if(!s)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return r.createElement(ie,(0,c.A)({exact:!0},i,{isActive:()=>a?.sidebar===t,label:n??s.label,to:s.path}))},docsVersion:function(e){let{label:t,to:n,docsPluginId:o,...i}=e;const a=(0,Re.Vd)(o)[0],s=t??a.label,l=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(a).path;return r.createElement(ie,(0,c.A)({},i,{label:s,to:l}))},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:o,dropdownItemsBefore:i,dropdownItemsAfter:a,...u}=e;const{search:d,hash:f}=(0,s.zy)(),p=(0,Ie.zK)(n),h=(0,Ie.jh)(n),{savePreferredVersionName:m}=(0,Ne.g1)(n),g=[...i,...h.map((e=>{const t=p.alternateDocVersions[e.name]??je(e);return{label:e.label,to:`${t.path}${d}${f}`,isActive:()=>e===p.activeVersion,onClick:()=>m(e.name)}})),...a],b=(0,Re.Vd)(n)[0],v=t&&g.length>1?(0,l.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):b.label,y=t&&g.length>1?void 0:je(b).path;return g.length<=1?r.createElement(ie,(0,c.A)({},u,{mobile:t,label:v,to:y,isActive:o?()=>!1:void 0})):r.createElement(fe,(0,c.A)({},u,{mobile:t,label:v,to:y,items:g,isActive:o?()=>!1:void 0}))}};function Me(e){let{type:t,...n}=e;const o=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),i=Le[o];if(!i)throw new Error(`No NavbarItem component found for type "${t}".`);return r.createElement(i,n)}function Be(){const e=(0,C.M)(),t=(0,x.p)().navbar.items;return r.createElement("ul",{className:"menu__list"},t.map(((t,n)=>r.createElement(Me,(0,c.A)({mobile:!0},t,{onClick:()=>e.toggle(),key:n})))))}function Fe(e){return r.createElement("button",(0,c.A)({},e,{type:"button",className:"clean-btn navbar-sidebar__back"}),r.createElement(l.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"},"\u2190 Back to main menu"))}function ze(){const e=0===(0,x.p)().navbar.items.length,t=L();return r.createElement(r.Fragment,null,!e&&r.createElement(Fe,{onClick:()=>t.hide()}),t.content)}function Ue(){const e=(0,C.M)();var t;return void 0===(t=e.shown)&&(t=!0),(0,r.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?r.createElement(M,{header:r.createElement(Q,null),primaryMenu:r.createElement(Be,null),secondaryMenu:r.createElement(ze,null)}):null}const $e={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function qe(e){return r.createElement("div",(0,c.A)({role:"presentation"},e,{className:(0,o.A)("navbar-sidebar__backdrop",e.className)}))}function He(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:i}}=(0,x.p)(),a=(0,C.M)(),{navbarRef:c,isNavbarVisible:s}=function(e){const[t,n]=(0,r.useState)(e),o=(0,r.useRef)(!1),i=(0,r.useRef)(0),a=(0,r.useCallback)((e=>{null!==e&&(i.current=e.getBoundingClientRect().height)}),[]);return(0,T.Mq)(((t,r)=>{let{scrollY:a}=t;if(!e)return;if(a=c?n(!1):a+l{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return o.current=!0,void n(!1);n(!0)})),{navbarRef:a,isNavbarVisible:t}}(n);return r.createElement("nav",{ref:c,"aria-label":(0,l.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,o.A)("navbar","navbar--fixed-top",n&&[$e.navbarHideable,!s&&$e.navbarHidden],{"navbar--dark":"dark"===i,"navbar--primary":"primary"===i,"navbar-sidebar--show":a.shown})},t,r.createElement(qe,{onClick:a.toggle}),r.createElement(Ue,null))}var Ve=n(70440);const We={errorBoundaryError:"errorBoundaryError_a6uf"};function Ge(e){return r.createElement("button",(0,c.A)({type:"button"},e),r.createElement(l.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error"},"Try again"))}function Ke(e){let{error:t}=e;const n=(0,Ve.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return r.createElement("p",{className:We.errorBoundaryError},n)}class Ye extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const Qe="right";function Ze(e){let{width:t=19,height:n=12,className:o,...i}=e;return r.createElement("svg",(0,c.A)({className:o,width:t,height:n,viewBox:"0 0 19 12",fill:"currentColor","aria-hidden":"true"},i),r.createElement("rect",{x:"0.5",y:"5.5",width:"18",height:"1",rx:"0.5",stroke:"currentColor",strokeLinecap:"round"}),r.createElement("rect",{x:"0.5",y:"10.5",width:"18",height:"1",rx:"0.5",stroke:"currentColor",strokeLinecap:"round"}),r.createElement("rect",{x:"0.5",y:"0.5",width:"18",height:"1",rx:"0.5",stroke:"currentColor",strokeLinecap:"round"}))}function Xe(){const{toggle:e,shown:t}=(0,C.M)();return r.createElement("button",{onClick:e,"aria-label":(0,l.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button"},r.createElement(Ze,null))}const Je={colorModeToggle:"colorModeToggle_DEke"};function et(e){let{items:t}=e;return r.createElement(r.Fragment,null,t.map(((e,t)=>r.createElement(Ye,{key:t,onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t})},r.createElement(Me,e)))))}function tt(e){let{left:t,right:n}=e;return r.createElement("div",{className:"navbar__inner"},r.createElement("div",{className:"navbar__items"},t),r.createElement("div",{className:"navbar__items navbar__items--right"},n))}function nt(){const e=(0,C.M)(),t=(0,x.p)().navbar.items,[n,o]=function(e){function t(e){return"left"===(e.position??Qe)}return[e.filter(t),e.filter((e=>!t(e)))]}(t),i=t.find((e=>"search"===e.type));return r.createElement(tt,{left:r.createElement(r.Fragment,null,!e.disabled&&r.createElement(Xe,null),r.createElement(K,null),r.createElement(et,{items:n})),right:r.createElement(r.Fragment,null,r.createElement(et,{items:o}),r.createElement(W,{className:Je.colorModeToggle}),!i&&r.createElement(Pe,null,r.createElement(Ae,null)))})}function rt(){return r.createElement(He,null,r.createElement(nt,null))}var ot=n(2543);function it(e){let{item:t}=e;const{to:n,href:i,label:a,prependBaseUrlToHref:s,...l}=t,u=(0,X.A)(n),d=(0,X.A)(i,{forcePrependBaseUrl:!0});return r.createElement(Z.A,(0,c.A)({},i?{href:s?d:i}:{to:u},l,{className:(0,o.A)("footer__link-item",!!i&&`footer__link-item_${(0,ot.kebabCase)(t.label)}`)}),a,i&&!(0,J.A)(i))}function at(e){let{item:t}=e;return t.html?r.createElement("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:t.html}}):r.createElement("li",{key:t.href??t.to,className:"footer__item"},r.createElement(it,{item:t}))}function ct(e){let{column:t}=e;return r.createElement("div",{className:(0,o.A)("col footer__col",`footer__col_${(0,ot.kebabCase)(t.title)}`)},r.createElement("div",{className:"footer__title"},t.title),r.createElement("ul",{className:"footer__items clean-list"},t.items.map(((e,t)=>r.createElement(at,{key:t,item:e})))))}function st(e){let{links:t}=e;return r.createElement("div",{className:"row footer__links"},t.map(((e,t)=>r.createElement(ct,{key:t,column:e}))))}var lt=n(41653);const ut="footerLogoLink_DDai";function dt(e){let{logo:t}=e;const{withBaseUrl:n}=(0,X.h)(),i={light:n(t.src),dark:n(t.srcDark??t.src)};return r.createElement(lt.A,{className:(0,o.A)("footer__logo",t.className),alt:t.alt,sources:i,width:t.width,height:t.height,style:t.style})}function ft(e){let{logo:t}=e;return t.href?r.createElement(Z.A,{href:t.href,className:ut,target:t.target},r.createElement(dt,{logo:t})):r.createElement(dt,{logo:t})}function pt(e){let{copyright:t}=e;return r.createElement("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function ht(e){let{style:t,links:n,logo:i,copyright:a}=e;return r.createElement("footer",{className:(0,o.A)("footer",{"footer--dark":"dark"===t})},r.createElement("div",{className:"container container-fluid"},n,(i||a)&&r.createElement("div",{className:"footer__bottom text--center"},i&&r.createElement("div",{className:"margin-bottom--sm"},i),a)))}function mt(){const{footer:e}=(0,x.p)();if(!e)return null;const{copyright:t,links:n,logo:o,style:i}=e;return r.createElement(ht,{style:i,links:n&&n.length>0&&r.createElement(st,{links:n}),logo:o&&r.createElement(ft,{logo:o}),copyright:t&&r.createElement(pt,{copyright:t})})}const gt=r.memo(mt),bt=(0,P.fM)([B.a,w.oq,T.Tv,Ne.VQ,a.Jx,function(e){let{children:t}=e;return r.createElement(I.y_,null,r.createElement(C.e,null,r.createElement(N,null,t)))}]);function vt(e){let{children:t}=e;return r.createElement(bt,null,t)}function yt(e){let{error:t,tryAgain:n}=e;return r.createElement("main",{className:"container margin-vert--xl"},r.createElement("div",{className:"row"},r.createElement("div",{className:"col col--6 col--offset-3"},r.createElement("h1",{className:"hero__title"},r.createElement(l.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),r.createElement("div",{className:"margin-vert--lg"},r.createElement(Ge,{onClick:n,className:"button button--primary shadow--lw"})),r.createElement("hr",null),r.createElement("div",{className:"margin-vert--md"},r.createElement(Ke,{error:t})))))}const xt={mainWrapper:"mainWrapper_z2l0"};function wt(e){const{children:t,noFooter:n,wrapperClassName:c,title:s,description:l}=e;return(0,b.J)(),r.createElement(vt,null,r.createElement(a.be,{title:s,description:l}),r.createElement(y,null),r.createElement(O,null),r.createElement(rt,null),r.createElement("div",{id:d,className:(0,o.A)(g.G.wrapper.main,xt.mainWrapper,c)},r.createElement(i.A,{fallback:e=>r.createElement(yt,e)},t)),!n&&r.createElement(gt,null))}},23465:(e,t,n)=>{"use strict";n.d(t,{A:()=>d});var r=n(58168),o=n(96540),i=n(75489),a=n(86025),c=n(44586),s=n(6342),l=n(41653);function u(e){let{logo:t,alt:n,imageClassName:r}=e;const i={light:(0,a.A)(t.src),dark:(0,a.A)(t.srcDark||t.src)},c=o.createElement(l.A,{className:t.className,sources:i,height:t.height,width:t.width,alt:n,style:t.style});return r?o.createElement("div",{className:r},c):c}function d(e){const{siteConfig:{title:t}}=(0,c.A)(),{navbar:{title:n,logo:l}}=(0,s.p)(),{imageClassName:d,titleClassName:f,...p}=e,h=(0,a.A)(l?.href||"/"),m=n?"":t,g=l?.alt??m;return o.createElement(i.A,(0,r.A)({to:h},p,l?.target&&{target:l.target}),l&&o.createElement(u,{logo:l,alt:g,imageClassName:d}),null!=n&&o.createElement("b",{className:f},n))}},41463:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(96540),o=n(5260);function i(e){let{locale:t,version:n,tag:i}=e;const a=t;return r.createElement(o.A,null,t&&r.createElement("meta",{name:"docusaurus_locale",content:t}),n&&r.createElement("meta",{name:"docusaurus_version",content:n}),i&&r.createElement("meta",{name:"docusaurus_tag",content:i}),a&&r.createElement("meta",{name:"docsearch:language",content:a}),n&&r.createElement("meta",{name:"docsearch:version",content:n}),i&&r.createElement("meta",{name:"docsearch:docusaurus_tag",content:i}))}},41653:(e,t,n)=>{"use strict";n.d(t,{A:()=>l});var r=n(58168),o=n(96540),i=n(20053),a=n(92303),c=n(95293);const s={themedImage:"themedImage_ToTc","themedImage--light":"themedImage--light_HNdA","themedImage--dark":"themedImage--dark_i4oU"};function l(e){const t=(0,a.A)(),{colorMode:n}=(0,c.G)(),{sources:l,className:u,alt:d,...f}=e,p=t?"dark"===n?["dark"]:["light"]:["light","dark"];return o.createElement(o.Fragment,null,p.map((e=>o.createElement("img",(0,r.A)({key:e,src:l[e],alt:d,className:(0,i.A)(s.themedImage,s[`themedImage--${e}`],u)},f)))))}},41422:(e,t,n)=>{"use strict";n.d(t,{N:()=>g,u:()=>s});var r=n(58168),o=n(96540),i=n(38193),a=n(53109);const c="ease-in-out";function s(e){let{initialState:t}=e;const[n,r]=(0,o.useState)(t??!1),i=(0,o.useCallback)((()=>{r((e=>!e))}),[]);return{collapsed:n,setCollapsed:r,toggleCollapsed:i}}const l={display:"none",overflow:"hidden",height:"0px"},u={display:"block",overflow:"visible",height:"auto"};function d(e,t){const n=t?l:u;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function f(e){let{collapsibleRef:t,collapsed:n,animation:r}=e;const i=(0,o.useRef)(!1);(0,o.useEffect)((()=>{const e=t.current;function o(){const t=e.scrollHeight,n=r?.duration??function(e){if((0,a.O)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(t);return{transition:`height ${n}ms ${r?.easing??c}`,height:`${t}px`}}function s(){const t=o();e.style.transition=t.transition,e.style.height=t.height}if(!i.current)return d(e,n),void(i.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(s(),requestAnimationFrame((()=>{e.style.height=l.height,e.style.overflow=l.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{s()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,r])}function p(e){if(!i.A.canUseDOM)return e?l:u}function h(e){let{as:t="div",collapsed:n,children:r,animation:i,onCollapseTransitionEnd:a,className:c,disableSSRStyle:s}=e;const l=(0,o.useRef)(null);return f({collapsibleRef:l,collapsed:n,animation:i}),o.createElement(t,{ref:l,style:s?void 0:p(n),onTransitionEnd:e=>{"height"===e.propertyName&&(d(l.current,n),a?.(n))},className:c},r)}function m(e){let{collapsed:t,...n}=e;const[i,a]=(0,o.useState)(!t),[c,s]=(0,o.useState)(t);return(0,o.useLayoutEffect)((()=>{t||a(!0)}),[t]),(0,o.useLayoutEffect)((()=>{i&&s(t)}),[i,t]),i?o.createElement(h,(0,r.A)({},n,{collapsed:c})):null}function g(e){let{lazy:t,...n}=e;const r=t?m:h;return o.createElement(r,n)}},65041:(e,t,n)=>{"use strict";n.d(t,{Mj:()=>h,oq:()=>p});var r=n(96540),o=n(92303),i=n(89466),a=n(89532),c=n(6342);const s=(0,i.Wf)("docusaurus.announcement.dismiss"),l=(0,i.Wf)("docusaurus.announcement.id"),u=()=>"true"===s.get(),d=e=>s.set(String(e)),f=r.createContext(null);function p(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,c.p)(),t=(0,o.A)(),[n,i]=(0,r.useState)((()=>!!t&&u()));(0,r.useEffect)((()=>{i(u())}),[]);const a=(0,r.useCallback)((()=>{d(!0),i(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=l.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;l.set(t),r&&d(!1),!r&&u()||i(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:a})),[e,n,a])}();return r.createElement(f.Provider,{value:n},t)}function h(){const e=(0,r.useContext)(f);if(!e)throw new a.dV("AnnouncementBarProvider");return e}},95293:(e,t,n)=>{"use strict";n.d(t,{G:()=>g,a:()=>m});var r=n(96540),o=n(38193),i=n(89532),a=n(89466),c=n(6342);const s=r.createContext(void 0),l="theme",u=(0,a.Wf)(l),d={light:"light",dark:"dark"},f=e=>e===d.dark?d.dark:d.light,p=e=>o.A.canUseDOM?f(document.documentElement.getAttribute("data-theme")):f(e),h=e=>{u.set(f(e))};function m(e){let{children:t}=e;const n=function(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,c.p)(),[o,i]=(0,r.useState)(p(e));(0,r.useEffect)((()=>{t&&u.del()}),[t]);const a=(0,r.useCallback)((function(t,r){void 0===r&&(r={});const{persist:o=!0}=r;t?(i(t),o&&h(t)):(i(n?window.matchMedia("(prefers-color-scheme: dark)").matches?d.dark:d.light:e),u.del())}),[n,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",f(o))}),[o]),(0,r.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==l)return;const t=u.get();null!==t&&a(f(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,a]);const s=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||s.current?s.current=window.matchMedia("print").matches:a(null)};return e.addListener(r),()=>e.removeListener(r)}),[a,t,n]),(0,r.useMemo)((()=>({colorMode:o,setColorMode:a,get isDarkTheme(){return o===d.dark},setLightTheme(){a(d.light)},setDarkTheme(){a(d.dark)}})),[o,a])}();return r.createElement(s.Provider,{value:n},t)}function g(){const e=(0,r.useContext)(s);if(null==e)throw new i.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},55597:(e,t,n)=>{"use strict";n.d(t,{VQ:()=>g,XK:()=>y,g1:()=>v});var r=n(96540),o=n(44070),i=n(17065),a=n(6342),c=n(84142),s=n(89532),l=n(89466);const u=e=>`docs-preferred-version-${e}`,d={save:(e,t,n)=>{(0,l.Wf)(u(e),{persistence:t}).set(n)},read:(e,t)=>(0,l.Wf)(u(e),{persistence:t}).get(),clear:(e,t)=>{(0,l.Wf)(u(e),{persistence:t}).del()}},f=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const p=r.createContext(null);function h(){const e=(0,o.Gy)(),t=(0,a.p)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[i,c]=(0,r.useState)((()=>f(n)));(0,r.useEffect)((()=>{c(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:r}=e;function o(e){const t=d.read(e,n);return r[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(d.clear(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,o(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[i,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){d.save(e,t,n),c((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function m(e){let{children:t}=e;const n=h();return r.createElement(p.Provider,{value:n},t)}function g(e){let{children:t}=e;return c.C5?r.createElement(m,null,t):r.createElement(r.Fragment,null,t)}function b(){const e=(0,r.useContext)(p);if(!e)throw new s.dV("DocsPreferredVersionContextProvider");return e}function v(e){void 0===e&&(e=i.W);const t=(0,o.ht)(e),[n,a]=b(),{preferredVersionName:c}=n[e];return{preferredVersion:t.versions.find((e=>e.name===c))??null,savePreferredVersionName:(0,r.useCallback)((t=>{a.savePreferredVersion(e,t)}),[a,e])}}function y(){const e=(0,o.Gy)(),[t]=b();function n(n){const r=e[n],{preferredVersionName:o}=t[n];return r.versions.find((e=>e.name===o))??null}const r=Object.keys(e);return Object.fromEntries(r.map((e=>[e,n(e)])))}},26588:(e,t,n)=>{"use strict";n.d(t,{V:()=>c,t:()=>s});var r=n(96540),o=n(89532);const i=Symbol("EmptyContext"),a=r.createContext(i);function c(e){let{children:t,name:n,items:o}=e;const i=(0,r.useMemo)((()=>n&&o?{name:n,items:o}:null),[n,o]);return r.createElement(a.Provider,{value:i},t)}function s(){const e=(0,r.useContext)(a);if(e===i)throw new o.dV("DocsSidebarProvider");return e}},22069:(e,t,n)=>{"use strict";n.d(t,{M:()=>f,e:()=>d});var r=n(96540),o=n(75600),i=n(24581),a=n(57485),c=n(6342),s=n(89532);const l=r.createContext(void 0);function u(){const e=function(){const e=(0,o.YL)(),{items:t}=(0,c.p)().navbar;return 0===t.length&&!e.component}(),t=(0,i.l)(),n=!e&&"mobile"===t,[s,l]=(0,r.useState)(!1);(0,a.$Z)((()=>{if(s)return l(!1),!1}));const u=(0,r.useCallback)((()=>{l((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&l(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:u,shown:s})),[e,n,u,s])}function d(e){let{children:t}=e;const n=u();return r.createElement(l.Provider,{value:n},t)}function f(){const e=r.useContext(l);if(void 0===e)throw new s.dV("NavbarMobileSidebarProvider");return e}},75600:(e,t,n)=>{"use strict";n.d(t,{GX:()=>s,YL:()=>c,y_:()=>a});var r=n(96540),o=n(89532);const i=r.createContext(null);function a(e){let{children:t}=e;const n=(0,r.useState)({component:null,props:null});return r.createElement(i.Provider,{value:n},t)}function c(){const e=(0,r.useContext)(i);if(!e)throw new o.dV("NavbarSecondaryMenuContentProvider");return e[0]}function s(e){let{component:t,props:n}=e;const a=(0,r.useContext)(i);if(!a)throw new o.dV("NavbarSecondaryMenuContentProvider");const[,c]=a,s=(0,o.Be)(n);return(0,r.useEffect)((()=>{c({component:t,props:s})}),[c,t,s]),(0,r.useEffect)((()=>()=>c({component:null,props:null})),[c]),null}},14090:(e,t,n)=>{"use strict";n.d(t,{w:()=>o,J:()=>i});var r=n(96540);const o="navigation-with-keyboard";function i(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(o),"mousedown"===e.type&&document.body.classList.remove(o)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(o),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},24255:(e,t,n)=>{"use strict";n.d(t,{b:()=>c,w:()=>s});var r=n(96540),o=n(44586),i=n(57485);const a="q";function c(){return(0,i.l)(a)}function s(){const{siteConfig:{baseUrl:e,themeConfig:t}}=(0,o.A)(),{algolia:{searchPagePath:n}}=t;return(0,r.useCallback)((t=>`${e}${n}?${a}=${encodeURIComponent(t)}`),[e,n])}},24581:(e,t,n)=>{"use strict";n.d(t,{l:()=>l});var r=n(96540),o=n(38193);const i={desktop:"desktop",mobile:"mobile",ssr:"ssr"},a=996;function c(){return o.A.canUseDOM?window.innerWidth>a?i.desktop:i.mobile:i.ssr}const s=!1;function l(){const[e,t]=(0,r.useState)((()=>s?"ssr":c()));return(0,r.useEffect)((()=>{function e(){t(c())}const n=s?window.setTimeout(e,1e3):void 0;return window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e),clearTimeout(n)}}),[]),e}},17559:(e,t,n)=>{"use strict";n.d(t,{G:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{}}},53109:(e,t,n)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{O:()=>r})},84142:(e,t,n)=>{"use strict";n.d(t,{C5:()=>d,OF:()=>b,QB:()=>x,Vd:()=>v,_o:()=>f,fW:()=>y,mz:()=>w,w8:()=>m});var r=n(96540),o=n(56347),i=n(22831),a=n(44070),c=n(55597),s=n(26588),l=n(31682),u=n(99169);const d=!!a.Gy;function f(e){if(e.href)return e.href;for(const t of e.items){if("link"===t.type)return t.href;if("category"===t.type){const e=f(t);if(e)return e}}}const p=(e,t)=>void 0!==e&&(0,u.ys)(e,t),h=(e,t)=>e.some((e=>m(e,t)));function m(e,t){return"link"===e.type?p(e.href,t):"category"===e.type&&(p(e.href,t)||h(e.items,t))}function g(e){let{sidebarItems:t,pathname:n,onlyCategories:r=!1}=e;const o=[];return function e(t){for(const i of t)if("category"===i.type&&((0,u.ys)(i.href,n)||e(i.items))||"link"===i.type&&(0,u.ys)(i.href,n)){return r&&"category"!==i.type||o.unshift(i),!0}return!1}(t),o}function b(){const e=(0,s.t)(),{pathname:t}=(0,o.zy)(),n=(0,a.vT)()?.pluginData.breadcrumbs;return!1!==n&&e?g({sidebarItems:e.items,pathname:t}):null}function v(e){const{activeVersion:t}=(0,a.zK)(e),{preferredVersion:n}=(0,c.g1)(e),o=(0,a.r7)(e);return(0,r.useMemo)((()=>(0,l.s)([t,n,o].filter(Boolean))),[t,n,o])}function y(e,t){const n=v(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map((e=>e[0])).join("\n- ")}`);return r[1]}),[e,n])}function x(e,t){const n=v(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${(0,l.s)(t.map((e=>e.id))).join("\n- ")}`)}return r}),[e,n])}function w(e){let{route:t,versionMetadata:n}=e;const r=(0,o.zy)(),a=t.routes,c=a.find((e=>(0,o.B6)(r.pathname,e)));if(!c)return null;const s=c.sidebar,l=s?n.docsSidebars[s]:void 0;return{docElement:(0,i.v)(a),sidebarName:s,sidebarItems:l}}},20481:(e,t,n)=>{"use strict";n.d(t,{s:()=>o});var r=n(44586);function o(e){const{siteConfig:t}=(0,r.A)(),{title:n,titleDelimiter:o}=t;return e?.trim().length?`${e.trim()} ${o} ${n}`:n}},57485:(e,t,n)=>{"use strict";n.d(t,{$Z:()=>c,aZ:()=>s,l:()=>l});var r=n(96540),o=n(56347),i=n(19888),a=n(89532);function c(e){!function(e){const t=(0,o.W6)(),n=(0,a._q)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}function s(e){return function(e){const t=(0,o.W6)();return(0,i.useSyncExternalStore)(t.listen,(()=>e(t)),(()=>e(t)))}((t=>null===e?null:new URLSearchParams(t.location.search).get(e)))}function l(e){const t=s(e)??"",n=function(){const e=(0,o.W6)();return(0,r.useCallback)(((t,n,r)=>{const o=new URLSearchParams(e.location.search);n?o.set(t,n):o.delete(t),(r?.push?e.push:e.replace)({search:o.toString()})}),[e])}();return[t,(0,r.useCallback)(((t,r)=>{n(e,t,r)}),[n,e])]}},31682:(e,t,n)=>{"use strict";function r(e,t){return void 0===t&&(t=(e,t)=>e===t),e.filter(((n,r)=>e.findIndex((e=>t(e,n)))!==r))}function o(e){return Array.from(new Set(e))}n.d(t,{X:()=>r,s:()=>o})},69024:(e,t,n)=>{"use strict";n.d(t,{e3:()=>f,be:()=>u,Jx:()=>p});var r=n(96540),o=n(20053),i=n(5260),a=n(53102);function c(){const e=r.useContext(a.o);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(86025),l=n(20481);function u(e){let{title:t,description:n,keywords:o,image:a,children:c}=e;const u=(0,l.s)(t),{withBaseUrl:d}=(0,s.h)(),f=a?d(a,{absolute:!0}):void 0;return r.createElement(i.A,null,t&&r.createElement("title",null,u),t&&r.createElement("meta",{property:"og:title",content:u}),n&&r.createElement("meta",{name:"description",content:n}),n&&r.createElement("meta",{property:"og:description",content:n}),o&&r.createElement("meta",{name:"keywords",content:Array.isArray(o)?o.join(","):o}),f&&r.createElement("meta",{property:"og:image",content:f}),f&&r.createElement("meta",{name:"twitter:image",content:f}),c)}const d=r.createContext(void 0);function f(e){let{className:t,children:n}=e;const a=r.useContext(d),c=(0,o.A)(a,t);return r.createElement(d.Provider,{value:c},r.createElement(i.A,null,r.createElement("html",{className:c})),n)}function p(e){let{children:t}=e;const n=c(),i=`plugin-${n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const a=`plugin-id-${n.plugin.id}`;return r.createElement(f,{className:(0,o.A)(i,a)},t)}},89532:(e,t,n)=>{"use strict";n.d(t,{Be:()=>s,ZC:()=>a,_q:()=>i,dV:()=>c,fM:()=>l});var r=n(96540);const o=n(38193).A.canUseDOM?r.useLayoutEffect:r.useEffect;function i(e){const t=(0,r.useRef)(e);return o((()=>{t.current=e}),[e]),(0,r.useCallback)((function(){return t.current(...arguments)}),[])}function a(e){const t=(0,r.useRef)();return o((()=>{t.current=e})),t.current}class c extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function s(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function l(e){return t=>{let{children:n}=t;return r.createElement(r.Fragment,null,e.reduceRight(((e,t)=>r.createElement(t,null,e)),n))}}},91252:(e,t,n)=>{"use strict";function r(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}n.d(t,{G:()=>r})},99169:(e,t,n)=>{"use strict";n.d(t,{Dt:()=>c,ys:()=>a});var r=n(96540),o=n(35947),i=n(44586);function a(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function c(){const{baseUrl:e}=(0,i.A)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function r(e){return e.path===t&&!0===e.exact}function o(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(r)||e(t.filter(o).flatMap((e=>e.routes??[])))}(n)}({routes:o.A,baseUrl:e})),[e])}},23104:(e,t,n)=>{"use strict";n.d(t,{Mq:()=>d,Tv:()=>s,a_:()=>f,gk:()=>p});var r=n(96540),o=n(38193),i=n(92303),a=n(89532);const c=r.createContext(void 0);function s(e){let{children:t}=e;const n=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return r.createElement(c.Provider,{value:n},t)}function l(){const e=(0,r.useContext)(c);if(null==e)throw new a.dV("ScrollControllerProvider");return e}const u=()=>o.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function d(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=l(),o=(0,r.useRef)(u()),i=(0,a._q)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=u();i(e,o.current),o.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[i,n,...t])}function f(){const e=l(),t=function(){const e=(0,r.useRef)({elem:null,top:0}),t=(0,r.useCallback)((t=>{e.current={elem:t,top:t.getBoundingClientRect().top}}),[]),n=(0,r.useCallback)((()=>{const{current:{elem:t,top:n}}=e;if(!t)return{restored:!1};const r=t.getBoundingClientRect().top-n;return r&&window.scrollBy({left:0,top:r}),e.current={elem:null,top:0},{restored:0!==r}}),[]);return(0,r.useMemo)((()=>({save:t,restore:n})),[n,t])}(),n=(0,r.useRef)(void 0),o=(0,r.useCallback)((r=>{t.save(r),e.disableScrollEvents(),n.current=()=>{const{restored:r}=t.restore();if(n.current=void 0,r){const t=()=>{e.enableScrollEvents(),window.removeEventListener("scroll",t)};window.addEventListener("scroll",t)}else e.enableScrollEvents()}}),[e,t]);return(0,r.useLayoutEffect)((()=>{queueMicrotask((()=>n.current?.()))})),{blockElementScrollPositionUntilNextRender:o}}function p(){const e=(0,r.useRef)(null),t=(0,i.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const o=document.documentElement.scrollTop;(n&&o>e||!n&&ot&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},2967:(e,t,n)=>{"use strict";n.d(t,{Cy:()=>a,af:()=>s,tU:()=>c});var r=n(44070),o=n(44586),i=n(55597);const a="default";function c(e,t){return`docs-${e}-${t}`}function s(){const{i18n:e}=(0,o.A)(),t=(0,r.Gy)(),n=(0,r.gk)(),s=(0,i.XK)();const l=[a,...Object.keys(t).map((function(e){const r=n?.activePlugin.pluginId===e?n.activeVersion:void 0,o=s[e],i=t[e].versions.find((e=>e.isLast));return c(e,(r??o??i).name)}))];return{locale:e.currentLocale,tags:l}}},89466:(e,t,n)=>{"use strict";n.d(t,{Dv:()=>d,Wf:()=>u});var r=n(96540),o=n(19888);const i="localStorage";function a(e){let{key:t,oldValue:n,newValue:r,storage:o}=e;if(n===r)return;const i=document.createEvent("StorageEvent");i.initStorageEvent("storage",!1,!1,t,n,r,window.location.href,o),window.dispatchEvent(i)}function c(e){if(void 0===e&&(e=i),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,s||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),s=!0),null}var t}let s=!1;const l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function u(e,t){if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(e);const n=c(t?.persistence);return null===n?l:{get:()=>{try{return n.getItem(e)}catch(t){return console.error(`Docusaurus storage error, can't get key=${e}`,t),null}},set:t=>{try{const r=n.getItem(e);n.setItem(e,t),a({key:e,oldValue:r,newValue:t,storage:n})}catch(r){console.error(`Docusaurus storage error, can't set ${e}=${t}`,r)}},del:()=>{try{const t=n.getItem(e);n.removeItem(e),a({key:e,oldValue:t,newValue:null,storage:n})}catch(t){console.error(`Docusaurus storage error, can't delete key=${e}`,t)}},listen:t=>{try{const r=r=>{r.storageArea===n&&r.key===e&&t(r)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}catch(r){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,r),()=>{}}}}}function d(e,t){const n=(0,r.useRef)((()=>null===e?l:u(e,t))).current(),i=(0,r.useCallback)((e=>"undefined"==typeof window?()=>{}:n.listen(e)),[n]);return[(0,o.useSyncExternalStore)(i,(()=>"undefined"==typeof window?null:n.get()),(()=>null)),n]}},32131:(e,t,n)=>{"use strict";n.d(t,{o:()=>a});var r=n(44586),o=n(56347),i=n(70440);function a(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:a,currentLocale:c}}=(0,r.A)(),{pathname:s}=(0,o.zy)(),l=(0,i.applyTrailingSlash)(s,{trailingSlash:n,baseUrl:e}),u=c===a?e:e.replace(`/${c}/`,"/"),d=l.replace(e,"");return{createUrl:function(e){let{locale:n,fullyQualified:r}=e;return`${r?t:""}${function(e){return e===a?`${u}`:`${u}${e}/`}(n)}${d}`}}}},75062:(e,t,n)=>{"use strict";n.d(t,{$:()=>a});var r=n(96540),o=n(56347),i=n(89532);function a(e){const t=(0,o.zy)(),n=(0,i.ZC)(t),a=(0,i._q)(e);(0,r.useEffect)((()=>{n&&t!==n&&a({location:t,previousLocation:n})}),[a,t,n])}},6342:(e,t,n)=>{"use strict";n.d(t,{p:()=>o});var r=n(44586);function o(){return(0,r.A)().siteConfig.themeConfig}},38126:(e,t,n)=>{"use strict";n.d(t,{c:()=>o});var r=n(44586);function o(){const{siteConfig:{themeConfig:e}}=(0,r.A)();return e}},51062:(e,t,n)=>{"use strict";n.d(t,{C:()=>c});var r=n(96540),o=n(91252),i=n(86025),a=n(38126);function c(){const{withBaseUrl:e}=(0,i.h)(),{algolia:{externalUrlRegex:t,replaceSearchResultPathname:n}}=(0,a.c)();return(0,r.useCallback)((r=>{const i=new URL(r);if((0,o.G)(t,i.href))return r;const a=`${i.pathname+i.hash}`;return e(function(e,t){return t?e.replaceAll(new RegExp(t.from,"g"),t.to):e}(a,n))}),[e,t,n])}},12983:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[o]=e.split(/[#?]/),i="/"===o||o===r?o:(a=o,n?function(e){return e.endsWith("/")?e:`${e}/`}(a):function(e){return e.endsWith("/")?e.slice(0,-1):e}(a));var a;return e.replace(o,i)}},80253:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=void 0,t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},70440:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="__blog-post-container";var o=n(12983);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(o).default}});var i=n(80253);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return i.getErrorCausalChain}})},20053:(e,t,n)=>{"use strict";function r(e){var t,n,o="";if("string"==typeof e||"number"==typeof e)o+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;to});const o=function(){for(var e,t,n=0,o="";n{"use strict";n.d(t,{zR:()=>x,TM:()=>D,yJ:()=>p,sC:()=>O,AO:()=>f});var r=n(58168);function o(e){return"/"===e.charAt(0)}function i(e,t){for(var n=t,r=n+1,o=e.length;r=0;f--){var p=a[f];"."===p?i(a,f):".."===p?(i(a,f),d++):d&&(i(a,f),d--)}if(!l)for(;d--;d)a.unshift("..");!l||""===a[0]||a[0]&&o(a[0])||a.unshift("");var h=a.join("/");return n&&"/"!==h.substr(-1)&&(h+="/"),h};var c=n(11561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function l(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function f(e){var t=e.pathname,n=e.search,r=e.hash,o=t||"/";return n&&"?"!==n&&(o+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(o+="#"===r.charAt(0)?r:"#"+r),o}function p(e,t,n,o){var i;"string"==typeof e?(i=function(e){var t=e||"/",n="",r="",o=t.indexOf("#");-1!==o&&(r=t.substr(o),t=t.substr(0,o));var i=t.indexOf("?");return-1!==i&&(n=t.substr(i),t=t.substr(0,i)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),i.state=t):(void 0===(i=(0,r.A)({},e)).pathname&&(i.pathname=""),i.search?"?"!==i.search.charAt(0)&&(i.search="?"+i.search):i.search="",i.hash?"#"!==i.hash.charAt(0)&&(i.hash="#"+i.hash):i.hash="",void 0!==t&&void 0===i.state&&(i.state=t));try{i.pathname=decodeURI(i.pathname)}catch(c){throw c instanceof URIError?new URIError('Pathname "'+i.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):c}return n&&(i.key=n),o?i.pathname?"/"!==i.pathname.charAt(0)&&(i.pathname=a(i.pathname,o.pathname)):i.pathname=o.pathname:i.pathname||(i.pathname="/"),i}function h(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,o){if(null!=e){var i="function"==typeof e?e(t,n):e;"string"==typeof i?"function"==typeof r?r(i,o):o(!0):o(!1!==i)}else o(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;rt?n.splice(t,n.length-t,o):n.push(o),d({action:r,location:o,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",o=p(e,t,m(),x.location);u.confirmTransitionTo(o,r,n,(function(e){e&&(x.entries[x.index]=o,d({action:r,location:o}))}))},go:y,goBack:function(){y(-1)},goForward:function(){y(1)},canGo:function(e){var t=x.index+e;return t>=0&&t{"use strict";var r=n(44363),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},i={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},a={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},c={};function s(e){return r.isMemo(e)?a:c[e.$$typeof]||o}c[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},c[r.Memo]=a;var l=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,f=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(h){var o=p(n);o&&o!==h&&e(t,o,r)}var a=u(n);d&&(a=a.concat(d(n)));for(var c=s(t),m=s(n),g=0;g{"use strict";e.exports=function(e,t,n,r,o,i,a,c){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,r,o,i,a,c],u=0;(s=new Error(t.replace(/%s/g,(function(){return l[u++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},2543:function(e,t,n){var r;e=n.nmd(e),function(){var o,i="Expected a function",a="__lodash_hash_undefined__",c="__lodash_placeholder__",s=16,l=32,u=64,d=128,f=256,p=1/0,h=9007199254740991,m=NaN,g=4294967295,b=[["ary",d],["bind",1],["bindKey",2],["curry",8],["curryRight",s],["flip",512],["partial",l],["partialRight",u],["rearg",f]],v="[object Arguments]",y="[object Array]",x="[object Boolean]",w="[object Date]",S="[object Error]",_="[object Function]",E="[object GeneratorFunction]",k="[object Map]",D="[object Number]",A="[object Object]",O="[object Promise]",C="[object RegExp]",T="[object Set]",P="[object String]",I="[object Symbol]",R="[object WeakMap]",N="[object ArrayBuffer]",j="[object DataView]",L="[object Float32Array]",M="[object Float64Array]",B="[object Int8Array]",F="[object Int16Array]",z="[object Int32Array]",U="[object Uint8Array]",$="[object Uint8ClampedArray]",q="[object Uint16Array]",H="[object Uint32Array]",V=/\b__p \+= '';/g,W=/\b(__p \+=) '' \+/g,G=/(__e\(.*?\)|\b__t\)) \+\n'';/g,K=/&(?:amp|lt|gt|quot|#39);/g,Y=/[&<>"']/g,Q=RegExp(K.source),Z=RegExp(Y.source),X=/<%-([\s\S]+?)%>/g,J=/<%([\s\S]+?)%>/g,ee=/<%=([\s\S]+?)%>/g,te=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,ne=/^\w*$/,re=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,oe=/[\\^$.*+?()[\]{}|]/g,ie=RegExp(oe.source),ae=/^\s+/,ce=/\s/,se=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,le=/\{\n\/\* \[wrapped with (.+)\] \*/,ue=/,? & /,de=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,fe=/[()=,{}\[\]\/\s]/,pe=/\\(\\)?/g,he=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,me=/\w*$/,ge=/^[-+]0x[0-9a-f]+$/i,be=/^0b[01]+$/i,ve=/^\[object .+?Constructor\]$/,ye=/^0o[0-7]+$/i,xe=/^(?:0|[1-9]\d*)$/,we=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Se=/($^)/,_e=/['\n\r\u2028\u2029\\]/g,Ee="\\ud800-\\udfff",ke="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",De="\\u2700-\\u27bf",Ae="a-z\\xdf-\\xf6\\xf8-\\xff",Oe="A-Z\\xc0-\\xd6\\xd8-\\xde",Ce="\\ufe0e\\ufe0f",Te="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Pe="['\u2019]",Ie="["+Ee+"]",Re="["+Te+"]",Ne="["+ke+"]",je="\\d+",Le="["+De+"]",Me="["+Ae+"]",Be="[^"+Ee+Te+je+De+Ae+Oe+"]",Fe="\\ud83c[\\udffb-\\udfff]",ze="[^"+Ee+"]",Ue="(?:\\ud83c[\\udde6-\\uddff]){2}",$e="[\\ud800-\\udbff][\\udc00-\\udfff]",qe="["+Oe+"]",He="\\u200d",Ve="(?:"+Me+"|"+Be+")",We="(?:"+qe+"|"+Be+")",Ge="(?:['\u2019](?:d|ll|m|re|s|t|ve))?",Ke="(?:['\u2019](?:D|LL|M|RE|S|T|VE))?",Ye="(?:"+Ne+"|"+Fe+")"+"?",Qe="["+Ce+"]?",Ze=Qe+Ye+("(?:"+He+"(?:"+[ze,Ue,$e].join("|")+")"+Qe+Ye+")*"),Xe="(?:"+[Le,Ue,$e].join("|")+")"+Ze,Je="(?:"+[ze+Ne+"?",Ne,Ue,$e,Ie].join("|")+")",et=RegExp(Pe,"g"),tt=RegExp(Ne,"g"),nt=RegExp(Fe+"(?="+Fe+")|"+Je+Ze,"g"),rt=RegExp([qe+"?"+Me+"+"+Ge+"(?="+[Re,qe,"$"].join("|")+")",We+"+"+Ke+"(?="+[Re,qe+Ve,"$"].join("|")+")",qe+"?"+Ve+"+"+Ge,qe+"+"+Ke,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",je,Xe].join("|"),"g"),ot=RegExp("["+He+Ee+ke+Ce+"]"),it=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,at=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],ct=-1,st={};st[L]=st[M]=st[B]=st[F]=st[z]=st[U]=st[$]=st[q]=st[H]=!0,st[v]=st[y]=st[N]=st[x]=st[j]=st[w]=st[S]=st[_]=st[k]=st[D]=st[A]=st[C]=st[T]=st[P]=st[R]=!1;var lt={};lt[v]=lt[y]=lt[N]=lt[j]=lt[x]=lt[w]=lt[L]=lt[M]=lt[B]=lt[F]=lt[z]=lt[k]=lt[D]=lt[A]=lt[C]=lt[T]=lt[P]=lt[I]=lt[U]=lt[$]=lt[q]=lt[H]=!0,lt[S]=lt[_]=lt[R]=!1;var ut={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},dt=parseFloat,ft=parseInt,pt="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,ht="object"==typeof self&&self&&self.Object===Object&&self,mt=pt||ht||Function("return this")(),gt=t&&!t.nodeType&&t,bt=gt&&e&&!e.nodeType&&e,vt=bt&&bt.exports===gt,yt=vt&&pt.process,xt=function(){try{var e=bt&&bt.require&&bt.require("util").types;return e||yt&&yt.binding&&yt.binding("util")}catch(t){}}(),wt=xt&&xt.isArrayBuffer,St=xt&&xt.isDate,_t=xt&&xt.isMap,Et=xt&&xt.isRegExp,kt=xt&&xt.isSet,Dt=xt&&xt.isTypedArray;function At(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function Ot(e,t,n,r){for(var o=-1,i=null==e?0:e.length;++o-1}function Nt(e,t,n){for(var r=-1,o=null==e?0:e.length;++r-1;);return n}function rn(e,t){for(var n=e.length;n--&&qt(t,e[n],0)>-1;);return n}var on=Kt({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"}),an=Kt({"&":"&","<":"<",">":">",'"':""","'":"'"});function cn(e){return"\\"+ut[e]}function sn(e){return ot.test(e)}function ln(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}function un(e,t){return function(n){return e(t(n))}}function dn(e,t){for(var n=-1,r=e.length,o=0,i=[];++n",""":'"',"'":"'"});var vn=function e(t){var n,r=(t=null==t?mt:vn.defaults(mt.Object(),t,vn.pick(mt,at))).Array,ce=t.Date,Ee=t.Error,ke=t.Function,De=t.Math,Ae=t.Object,Oe=t.RegExp,Ce=t.String,Te=t.TypeError,Pe=r.prototype,Ie=ke.prototype,Re=Ae.prototype,Ne=t["__core-js_shared__"],je=Ie.toString,Le=Re.hasOwnProperty,Me=0,Be=(n=/[^.]+$/.exec(Ne&&Ne.keys&&Ne.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",Fe=Re.toString,ze=je.call(Ae),Ue=mt._,$e=Oe("^"+je.call(Le).replace(oe,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),qe=vt?t.Buffer:o,He=t.Symbol,Ve=t.Uint8Array,We=qe?qe.allocUnsafe:o,Ge=un(Ae.getPrototypeOf,Ae),Ke=Ae.create,Ye=Re.propertyIsEnumerable,Qe=Pe.splice,Ze=He?He.isConcatSpreadable:o,Xe=He?He.iterator:o,Je=He?He.toStringTag:o,nt=function(){try{var e=pi(Ae,"defineProperty");return e({},"",{}),e}catch(t){}}(),ot=t.clearTimeout!==mt.clearTimeout&&t.clearTimeout,ut=ce&&ce.now!==mt.Date.now&&ce.now,pt=t.setTimeout!==mt.setTimeout&&t.setTimeout,ht=De.ceil,gt=De.floor,bt=Ae.getOwnPropertySymbols,yt=qe?qe.isBuffer:o,xt=t.isFinite,zt=Pe.join,Kt=un(Ae.keys,Ae),yn=De.max,xn=De.min,wn=ce.now,Sn=t.parseInt,_n=De.random,En=Pe.reverse,kn=pi(t,"DataView"),Dn=pi(t,"Map"),An=pi(t,"Promise"),On=pi(t,"Set"),Cn=pi(t,"WeakMap"),Tn=pi(Ae,"create"),Pn=Cn&&new Cn,In={},Rn=Fi(kn),Nn=Fi(Dn),jn=Fi(An),Ln=Fi(On),Mn=Fi(Cn),Bn=He?He.prototype:o,Fn=Bn?Bn.valueOf:o,zn=Bn?Bn.toString:o;function Un(e){if(nc(e)&&!Va(e)&&!(e instanceof Vn)){if(e instanceof Hn)return e;if(Le.call(e,"__wrapped__"))return zi(e)}return new Hn(e)}var $n=function(){function e(){}return function(t){if(!tc(t))return{};if(Ke)return Ke(t);e.prototype=t;var n=new e;return e.prototype=o,n}}();function qn(){}function Hn(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=o}function Vn(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=g,this.__views__=[]}function Wn(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t=t?e:t)),e}function lr(e,t,n,r,i,a){var c,s=1&t,l=2&t,u=4&t;if(n&&(c=i?n(e,r,i,a):n(e)),c!==o)return c;if(!tc(e))return e;var d=Va(e);if(d){if(c=function(e){var t=e.length,n=new e.constructor(t);t&&"string"==typeof e[0]&&Le.call(e,"index")&&(n.index=e.index,n.input=e.input);return n}(e),!s)return Po(e,c)}else{var f=gi(e),p=f==_||f==E;if(Ya(e))return ko(e,s);if(f==A||f==v||p&&!i){if(c=l||p?{}:vi(e),!s)return l?function(e,t){return Io(e,mi(e),t)}(e,function(e,t){return e&&Io(t,Rc(t),e)}(c,e)):function(e,t){return Io(e,hi(e),t)}(e,ir(c,e))}else{if(!lt[f])return i?e:{};c=function(e,t,n){var r=e.constructor;switch(t){case N:return Do(e);case x:case w:return new r(+e);case j:return function(e,t){var n=t?Do(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}(e,n);case L:case M:case B:case F:case z:case U:case $:case q:case H:return Ao(e,n);case k:return new r;case D:case P:return new r(e);case C:return function(e){var t=new e.constructor(e.source,me.exec(e));return t.lastIndex=e.lastIndex,t}(e);case T:return new r;case I:return o=e,Fn?Ae(Fn.call(o)):{}}var o}(e,f,s)}}a||(a=new Qn);var h=a.get(e);if(h)return h;a.set(e,c),cc(e)?e.forEach((function(r){c.add(lr(r,t,n,r,e,a))})):rc(e)&&e.forEach((function(r,o){c.set(o,lr(r,t,n,o,e,a))}));var m=d?o:(u?l?ai:ii:l?Rc:Ic)(e);return Ct(m||e,(function(r,o){m&&(r=e[o=r]),nr(c,o,lr(r,t,n,o,e,a))})),c}function ur(e,t,n){var r=n.length;if(null==e)return!r;for(e=Ae(e);r--;){var i=n[r],a=t[i],c=e[i];if(c===o&&!(i in e)||!a(c))return!1}return!0}function dr(e,t,n){if("function"!=typeof e)throw new Te(i);return Ii((function(){e.apply(o,n)}),t)}function fr(e,t,n,r){var o=-1,i=Rt,a=!0,c=e.length,s=[],l=t.length;if(!c)return s;n&&(t=jt(t,Jt(n))),r?(i=Nt,a=!1):t.length>=200&&(i=tn,a=!1,t=new Yn(t));e:for(;++o-1},Gn.prototype.set=function(e,t){var n=this.__data__,r=rr(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this},Kn.prototype.clear=function(){this.size=0,this.__data__={hash:new Wn,map:new(Dn||Gn),string:new Wn}},Kn.prototype.delete=function(e){var t=di(this,e).delete(e);return this.size-=t?1:0,t},Kn.prototype.get=function(e){return di(this,e).get(e)},Kn.prototype.has=function(e){return di(this,e).has(e)},Kn.prototype.set=function(e,t){var n=di(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this},Yn.prototype.add=Yn.prototype.push=function(e){return this.__data__.set(e,a),this},Yn.prototype.has=function(e){return this.__data__.has(e)},Qn.prototype.clear=function(){this.__data__=new Gn,this.size=0},Qn.prototype.delete=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n},Qn.prototype.get=function(e){return this.__data__.get(e)},Qn.prototype.has=function(e){return this.__data__.has(e)},Qn.prototype.set=function(e,t){var n=this.__data__;if(n instanceof Gn){var r=n.__data__;if(!Dn||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new Kn(r)}return n.set(e,t),this.size=n.size,this};var pr=jo(wr),hr=jo(Sr,!0);function mr(e,t){var n=!0;return pr(e,(function(e,r,o){return n=!!t(e,r,o)})),n}function gr(e,t,n){for(var r=-1,i=e.length;++r0&&n(c)?t>1?vr(c,t-1,n,r,o):Lt(o,c):r||(o[o.length]=c)}return o}var yr=Lo(),xr=Lo(!0);function wr(e,t){return e&&yr(e,t,Ic)}function Sr(e,t){return e&&xr(e,t,Ic)}function _r(e,t){return It(t,(function(t){return Xa(e[t])}))}function Er(e,t){for(var n=0,r=(t=wo(t,e)).length;null!=e&&nt}function Or(e,t){return null!=e&&Le.call(e,t)}function Cr(e,t){return null!=e&&t in Ae(e)}function Tr(e,t,n){for(var i=n?Nt:Rt,a=e[0].length,c=e.length,s=c,l=r(c),u=1/0,d=[];s--;){var f=e[s];s&&t&&(f=jt(f,Jt(t))),u=xn(f.length,u),l[s]=!n&&(t||a>=120&&f.length>=120)?new Yn(s&&f):o}f=e[0];var p=-1,h=l[0];e:for(;++p=c?s:s*("desc"==n[r]?-1:1)}return e.index-t.index}(e,t,n)}))}function Wr(e,t,n){for(var r=-1,o=t.length,i={};++r-1;)c!==e&&Qe.call(c,s,1),Qe.call(e,s,1);return e}function Kr(e,t){for(var n=e?t.length:0,r=n-1;n--;){var o=t[n];if(n==r||o!==i){var i=o;xi(o)?Qe.call(e,o,1):po(e,o)}}return e}function Yr(e,t){return e+gt(_n()*(t-e+1))}function Qr(e,t){var n="";if(!e||t<1||t>h)return n;do{t%2&&(n+=e),(t=gt(t/2))&&(e+=e)}while(t);return n}function Zr(e,t){return Ri(Oi(e,t,os),e+"")}function Xr(e){return Xn(Uc(e))}function Jr(e,t){var n=Uc(e);return Li(n,sr(t,0,n.length))}function eo(e,t,n,r){if(!tc(e))return e;for(var i=-1,a=(t=wo(t,e)).length,c=a-1,s=e;null!=s&&++ii?0:i+t),(n=n>i?i:n)<0&&(n+=i),i=t>n?0:n-t>>>0,t>>>=0;for(var a=r(i);++o>>1,a=e[i];null!==a&&!lc(a)&&(n?a<=t:a=200){var l=t?null:Zo(e);if(l)return fn(l);a=!1,o=tn,s=new Yn}else s=t?[]:c;e:for(;++r=r?e:oo(e,t,n)}var Eo=ot||function(e){return mt.clearTimeout(e)};function ko(e,t){if(t)return e.slice();var n=e.length,r=We?We(n):new e.constructor(n);return e.copy(r),r}function Do(e){var t=new e.constructor(e.byteLength);return new Ve(t).set(new Ve(e)),t}function Ao(e,t){var n=t?Do(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}function Oo(e,t){if(e!==t){var n=e!==o,r=null===e,i=e==e,a=lc(e),c=t!==o,s=null===t,l=t==t,u=lc(t);if(!s&&!u&&!a&&e>t||a&&c&&l&&!s&&!u||r&&c&&l||!n&&l||!i)return 1;if(!r&&!a&&!u&&e1?n[i-1]:o,c=i>2?n[2]:o;for(a=e.length>3&&"function"==typeof a?(i--,a):o,c&&wi(n[0],n[1],c)&&(a=i<3?o:a,i=1),t=Ae(t);++r-1?i[a?t[c]:c]:o}}function Uo(e){return oi((function(t){var n=t.length,r=n,a=Hn.prototype.thru;for(e&&t.reverse();r--;){var c=t[r];if("function"!=typeof c)throw new Te(i);if(a&&!s&&"wrapper"==si(c))var s=new Hn([],!0)}for(r=s?r:n;++r1&&x.reverse(),p&&us))return!1;var u=a.get(e),d=a.get(t);if(u&&d)return u==t&&d==e;var f=-1,p=!0,h=2&n?new Yn:o;for(a.set(e,t),a.set(t,e);++f-1&&e%1==0&&e1?"& ":"")+t[r],t=t.join(n>2?", ":" "),e.replace(se,"{\n/* [wrapped with "+t+"] */\n")}(r,function(e,t){return Ct(b,(function(n){var r="_."+n[0];t&n[1]&&!Rt(e,r)&&e.push(r)})),e.sort()}(function(e){var t=e.match(le);return t?t[1].split(ue):[]}(r),n)))}function ji(e){var t=0,n=0;return function(){var r=wn(),i=16-(r-n);if(n=r,i>0){if(++t>=800)return arguments[0]}else t=0;return e.apply(o,arguments)}}function Li(e,t){var n=-1,r=e.length,i=r-1;for(t=t===o?r:t;++n1?e[t-1]:o;return n="function"==typeof n?(e.pop(),n):o,aa(e,n)}));function pa(e){var t=Un(e);return t.__chain__=!0,t}function ha(e,t){return t(e)}var ma=oi((function(e){var t=e.length,n=t?e[0]:0,r=this.__wrapped__,i=function(t){return cr(t,e)};return!(t>1||this.__actions__.length)&&r instanceof Vn&&xi(n)?((r=r.slice(n,+n+(t?1:0))).__actions__.push({func:ha,args:[i],thisArg:o}),new Hn(r,this.__chain__).thru((function(e){return t&&!e.length&&e.push(o),e}))):this.thru(i)}));var ga=Ro((function(e,t,n){Le.call(e,n)?++e[n]:ar(e,n,1)}));var ba=zo(Hi),va=zo(Vi);function ya(e,t){return(Va(e)?Ct:pr)(e,ui(t,3))}function xa(e,t){return(Va(e)?Tt:hr)(e,ui(t,3))}var wa=Ro((function(e,t,n){Le.call(e,n)?e[n].push(t):ar(e,n,[t])}));var Sa=Zr((function(e,t,n){var o=-1,i="function"==typeof t,a=Ga(e)?r(e.length):[];return pr(e,(function(e){a[++o]=i?At(t,e,n):Pr(e,t,n)})),a})),_a=Ro((function(e,t,n){ar(e,n,t)}));function Ea(e,t){return(Va(e)?jt:zr)(e,ui(t,3))}var ka=Ro((function(e,t,n){e[n?0:1].push(t)}),(function(){return[[],[]]}));var Da=Zr((function(e,t){if(null==e)return[];var n=t.length;return n>1&&wi(e,t[0],t[1])?t=[]:n>2&&wi(t[0],t[1],t[2])&&(t=[t[0]]),Vr(e,vr(t,1),[])})),Aa=ut||function(){return mt.Date.now()};function Oa(e,t,n){return t=n?o:t,t=e&&null==t?e.length:t,Jo(e,d,o,o,o,o,t)}function Ca(e,t){var n;if("function"!=typeof t)throw new Te(i);return e=mc(e),function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=o),n}}var Ta=Zr((function(e,t,n){var r=1;if(n.length){var o=dn(n,li(Ta));r|=l}return Jo(e,r,t,n,o)})),Pa=Zr((function(e,t,n){var r=3;if(n.length){var o=dn(n,li(Pa));r|=l}return Jo(t,r,e,n,o)}));function Ia(e,t,n){var r,a,c,s,l,u,d=0,f=!1,p=!1,h=!0;if("function"!=typeof e)throw new Te(i);function m(t){var n=r,i=a;return r=a=o,d=t,s=e.apply(i,n)}function g(e){var n=e-u;return u===o||n>=t||n<0||p&&e-d>=c}function b(){var e=Aa();if(g(e))return v(e);l=Ii(b,function(e){var n=t-(e-u);return p?xn(n,c-(e-d)):n}(e))}function v(e){return l=o,h&&r?m(e):(r=a=o,s)}function y(){var e=Aa(),n=g(e);if(r=arguments,a=this,u=e,n){if(l===o)return function(e){return d=e,l=Ii(b,t),f?m(e):s}(u);if(p)return Eo(l),l=Ii(b,t),m(u)}return l===o&&(l=Ii(b,t)),s}return t=bc(t)||0,tc(n)&&(f=!!n.leading,c=(p="maxWait"in n)?yn(bc(n.maxWait)||0,t):c,h="trailing"in n?!!n.trailing:h),y.cancel=function(){l!==o&&Eo(l),d=0,r=u=a=l=o},y.flush=function(){return l===o?s:v(Aa())},y}var Ra=Zr((function(e,t){return dr(e,1,t)})),Na=Zr((function(e,t,n){return dr(e,bc(t)||0,n)}));function ja(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new Te(i);var n=function(){var r=arguments,o=t?t.apply(this,r):r[0],i=n.cache;if(i.has(o))return i.get(o);var a=e.apply(this,r);return n.cache=i.set(o,a)||i,a};return n.cache=new(ja.Cache||Kn),n}function La(e){if("function"!=typeof e)throw new Te(i);return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}ja.Cache=Kn;var Ma=So((function(e,t){var n=(t=1==t.length&&Va(t[0])?jt(t[0],Jt(ui())):jt(vr(t,1),Jt(ui()))).length;return Zr((function(r){for(var o=-1,i=xn(r.length,n);++o=t})),Ha=Ir(function(){return arguments}())?Ir:function(e){return nc(e)&&Le.call(e,"callee")&&!Ye.call(e,"callee")},Va=r.isArray,Wa=wt?Jt(wt):function(e){return nc(e)&&Dr(e)==N};function Ga(e){return null!=e&&ec(e.length)&&!Xa(e)}function Ka(e){return nc(e)&&Ga(e)}var Ya=yt||bs,Qa=St?Jt(St):function(e){return nc(e)&&Dr(e)==w};function Za(e){if(!nc(e))return!1;var t=Dr(e);return t==S||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!ic(e)}function Xa(e){if(!tc(e))return!1;var t=Dr(e);return t==_||t==E||"[object AsyncFunction]"==t||"[object Proxy]"==t}function Ja(e){return"number"==typeof e&&e==mc(e)}function ec(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=h}function tc(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function nc(e){return null!=e&&"object"==typeof e}var rc=_t?Jt(_t):function(e){return nc(e)&&gi(e)==k};function oc(e){return"number"==typeof e||nc(e)&&Dr(e)==D}function ic(e){if(!nc(e)||Dr(e)!=A)return!1;var t=Ge(e);if(null===t)return!0;var n=Le.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&je.call(n)==ze}var ac=Et?Jt(Et):function(e){return nc(e)&&Dr(e)==C};var cc=kt?Jt(kt):function(e){return nc(e)&&gi(e)==T};function sc(e){return"string"==typeof e||!Va(e)&&nc(e)&&Dr(e)==P}function lc(e){return"symbol"==typeof e||nc(e)&&Dr(e)==I}var uc=Dt?Jt(Dt):function(e){return nc(e)&&ec(e.length)&&!!st[Dr(e)]};var dc=Ko(Fr),fc=Ko((function(e,t){return e<=t}));function pc(e){if(!e)return[];if(Ga(e))return sc(e)?mn(e):Po(e);if(Xe&&e[Xe])return function(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}(e[Xe]());var t=gi(e);return(t==k?ln:t==T?fn:Uc)(e)}function hc(e){return e?(e=bc(e))===p||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}function mc(e){var t=hc(e),n=t%1;return t==t?n?t-n:t:0}function gc(e){return e?sr(mc(e),0,g):0}function bc(e){if("number"==typeof e)return e;if(lc(e))return m;if(tc(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=tc(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=Xt(e);var n=be.test(e);return n||ye.test(e)?ft(e.slice(2),n?2:8):ge.test(e)?m:+e}function vc(e){return Io(e,Rc(e))}function yc(e){return null==e?"":uo(e)}var xc=No((function(e,t){if(ki(t)||Ga(t))Io(t,Ic(t),e);else for(var n in t)Le.call(t,n)&&nr(e,n,t[n])})),wc=No((function(e,t){Io(t,Rc(t),e)})),Sc=No((function(e,t,n,r){Io(t,Rc(t),e,r)})),_c=No((function(e,t,n,r){Io(t,Ic(t),e,r)})),Ec=oi(cr);var kc=Zr((function(e,t){e=Ae(e);var n=-1,r=t.length,i=r>2?t[2]:o;for(i&&wi(t[0],t[1],i)&&(r=1);++n1),t})),Io(e,ai(e),n),r&&(n=lr(n,7,ni));for(var o=t.length;o--;)po(n,t[o]);return n}));var Mc=oi((function(e,t){return null==e?{}:function(e,t){return Wr(e,t,(function(t,n){return Oc(e,n)}))}(e,t)}));function Bc(e,t){if(null==e)return{};var n=jt(ai(e),(function(e){return[e]}));return t=ui(t),Wr(e,n,(function(e,n){return t(e,n[0])}))}var Fc=Xo(Ic),zc=Xo(Rc);function Uc(e){return null==e?[]:en(e,Ic(e))}var $c=Bo((function(e,t,n){return t=t.toLowerCase(),e+(n?qc(t):t)}));function qc(e){return Zc(yc(e).toLowerCase())}function Hc(e){return(e=yc(e))&&e.replace(we,on).replace(tt,"")}var Vc=Bo((function(e,t,n){return e+(n?"-":"")+t.toLowerCase()})),Wc=Bo((function(e,t,n){return e+(n?" ":"")+t.toLowerCase()})),Gc=Mo("toLowerCase");var Kc=Bo((function(e,t,n){return e+(n?"_":"")+t.toLowerCase()}));var Yc=Bo((function(e,t,n){return e+(n?" ":"")+Zc(t)}));var Qc=Bo((function(e,t,n){return e+(n?" ":"")+t.toUpperCase()})),Zc=Mo("toUpperCase");function Xc(e,t,n){return e=yc(e),(t=n?o:t)===o?function(e){return it.test(e)}(e)?function(e){return e.match(rt)||[]}(e):function(e){return e.match(de)||[]}(e):e.match(t)||[]}var Jc=Zr((function(e,t){try{return At(e,o,t)}catch(n){return Za(n)?n:new Ee(n)}})),es=oi((function(e,t){return Ct(t,(function(t){t=Bi(t),ar(e,t,Ta(e[t],e))})),e}));function ts(e){return function(){return e}}var ns=Uo(),rs=Uo(!0);function os(e){return e}function is(e){return Lr("function"==typeof e?e:lr(e,1))}var as=Zr((function(e,t){return function(n){return Pr(n,e,t)}})),cs=Zr((function(e,t){return function(n){return Pr(e,n,t)}}));function ss(e,t,n){var r=Ic(t),o=_r(t,r);null!=n||tc(t)&&(o.length||!r.length)||(n=t,t=e,e=this,o=_r(t,Ic(t)));var i=!(tc(n)&&"chain"in n&&!n.chain),a=Xa(e);return Ct(o,(function(n){var r=t[n];e[n]=r,a&&(e.prototype[n]=function(){var t=this.__chain__;if(i||t){var n=e(this.__wrapped__);return(n.__actions__=Po(this.__actions__)).push({func:r,args:arguments,thisArg:e}),n.__chain__=t,n}return r.apply(e,Lt([this.value()],arguments))})})),e}function ls(){}var us=Vo(jt),ds=Vo(Pt),fs=Vo(Ft);function ps(e){return Si(e)?Gt(Bi(e)):function(e){return function(t){return Er(t,e)}}(e)}var hs=Go(),ms=Go(!0);function gs(){return[]}function bs(){return!1}var vs=Ho((function(e,t){return e+t}),0),ys=Qo("ceil"),xs=Ho((function(e,t){return e/t}),1),ws=Qo("floor");var Ss,_s=Ho((function(e,t){return e*t}),1),Es=Qo("round"),ks=Ho((function(e,t){return e-t}),0);return Un.after=function(e,t){if("function"!=typeof t)throw new Te(i);return e=mc(e),function(){if(--e<1)return t.apply(this,arguments)}},Un.ary=Oa,Un.assign=xc,Un.assignIn=wc,Un.assignInWith=Sc,Un.assignWith=_c,Un.at=Ec,Un.before=Ca,Un.bind=Ta,Un.bindAll=es,Un.bindKey=Pa,Un.castArray=function(){if(!arguments.length)return[];var e=arguments[0];return Va(e)?e:[e]},Un.chain=pa,Un.chunk=function(e,t,n){t=(n?wi(e,t,n):t===o)?1:yn(mc(t),0);var i=null==e?0:e.length;if(!i||t<1)return[];for(var a=0,c=0,s=r(ht(i/t));ai?0:i+n),(r=r===o||r>i?i:mc(r))<0&&(r+=i),r=n>r?0:gc(r);n>>0)?(e=yc(e))&&("string"==typeof t||null!=t&&!ac(t))&&!(t=uo(t))&&sn(e)?_o(mn(e),0,n):e.split(t,n):[]},Un.spread=function(e,t){if("function"!=typeof e)throw new Te(i);return t=null==t?0:yn(mc(t),0),Zr((function(n){var r=n[t],o=_o(n,0,t);return r&&Lt(o,r),At(e,this,o)}))},Un.tail=function(e){var t=null==e?0:e.length;return t?oo(e,1,t):[]},Un.take=function(e,t,n){return e&&e.length?oo(e,0,(t=n||t===o?1:mc(t))<0?0:t):[]},Un.takeRight=function(e,t,n){var r=null==e?0:e.length;return r?oo(e,(t=r-(t=n||t===o?1:mc(t)))<0?0:t,r):[]},Un.takeRightWhile=function(e,t){return e&&e.length?mo(e,ui(t,3),!1,!0):[]},Un.takeWhile=function(e,t){return e&&e.length?mo(e,ui(t,3)):[]},Un.tap=function(e,t){return t(e),e},Un.throttle=function(e,t,n){var r=!0,o=!0;if("function"!=typeof e)throw new Te(i);return tc(n)&&(r="leading"in n?!!n.leading:r,o="trailing"in n?!!n.trailing:o),Ia(e,t,{leading:r,maxWait:t,trailing:o})},Un.thru=ha,Un.toArray=pc,Un.toPairs=Fc,Un.toPairsIn=zc,Un.toPath=function(e){return Va(e)?jt(e,Bi):lc(e)?[e]:Po(Mi(yc(e)))},Un.toPlainObject=vc,Un.transform=function(e,t,n){var r=Va(e),o=r||Ya(e)||uc(e);if(t=ui(t,4),null==n){var i=e&&e.constructor;n=o?r?new i:[]:tc(e)&&Xa(i)?$n(Ge(e)):{}}return(o?Ct:wr)(e,(function(e,r,o){return t(n,e,r,o)})),n},Un.unary=function(e){return Oa(e,1)},Un.union=na,Un.unionBy=ra,Un.unionWith=oa,Un.uniq=function(e){return e&&e.length?fo(e):[]},Un.uniqBy=function(e,t){return e&&e.length?fo(e,ui(t,2)):[]},Un.uniqWith=function(e,t){return t="function"==typeof t?t:o,e&&e.length?fo(e,o,t):[]},Un.unset=function(e,t){return null==e||po(e,t)},Un.unzip=ia,Un.unzipWith=aa,Un.update=function(e,t,n){return null==e?e:ho(e,t,xo(n))},Un.updateWith=function(e,t,n,r){return r="function"==typeof r?r:o,null==e?e:ho(e,t,xo(n),r)},Un.values=Uc,Un.valuesIn=function(e){return null==e?[]:en(e,Rc(e))},Un.without=ca,Un.words=Xc,Un.wrap=function(e,t){return Ba(xo(t),e)},Un.xor=sa,Un.xorBy=la,Un.xorWith=ua,Un.zip=da,Un.zipObject=function(e,t){return vo(e||[],t||[],nr)},Un.zipObjectDeep=function(e,t){return vo(e||[],t||[],eo)},Un.zipWith=fa,Un.entries=Fc,Un.entriesIn=zc,Un.extend=wc,Un.extendWith=Sc,ss(Un,Un),Un.add=vs,Un.attempt=Jc,Un.camelCase=$c,Un.capitalize=qc,Un.ceil=ys,Un.clamp=function(e,t,n){return n===o&&(n=t,t=o),n!==o&&(n=(n=bc(n))==n?n:0),t!==o&&(t=(t=bc(t))==t?t:0),sr(bc(e),t,n)},Un.clone=function(e){return lr(e,4)},Un.cloneDeep=function(e){return lr(e,5)},Un.cloneDeepWith=function(e,t){return lr(e,5,t="function"==typeof t?t:o)},Un.cloneWith=function(e,t){return lr(e,4,t="function"==typeof t?t:o)},Un.conformsTo=function(e,t){return null==t||ur(e,t,Ic(t))},Un.deburr=Hc,Un.defaultTo=function(e,t){return null==e||e!=e?t:e},Un.divide=xs,Un.endsWith=function(e,t,n){e=yc(e),t=uo(t);var r=e.length,i=n=n===o?r:sr(mc(n),0,r);return(n-=t.length)>=0&&e.slice(n,i)==t},Un.eq=Ua,Un.escape=function(e){return(e=yc(e))&&Z.test(e)?e.replace(Y,an):e},Un.escapeRegExp=function(e){return(e=yc(e))&&ie.test(e)?e.replace(oe,"\\$&"):e},Un.every=function(e,t,n){var r=Va(e)?Pt:mr;return n&&wi(e,t,n)&&(t=o),r(e,ui(t,3))},Un.find=ba,Un.findIndex=Hi,Un.findKey=function(e,t){return Ut(e,ui(t,3),wr)},Un.findLast=va,Un.findLastIndex=Vi,Un.findLastKey=function(e,t){return Ut(e,ui(t,3),Sr)},Un.floor=ws,Un.forEach=ya,Un.forEachRight=xa,Un.forIn=function(e,t){return null==e?e:yr(e,ui(t,3),Rc)},Un.forInRight=function(e,t){return null==e?e:xr(e,ui(t,3),Rc)},Un.forOwn=function(e,t){return e&&wr(e,ui(t,3))},Un.forOwnRight=function(e,t){return e&&Sr(e,ui(t,3))},Un.get=Ac,Un.gt=$a,Un.gte=qa,Un.has=function(e,t){return null!=e&&bi(e,t,Or)},Un.hasIn=Oc,Un.head=Gi,Un.identity=os,Un.includes=function(e,t,n,r){e=Ga(e)?e:Uc(e),n=n&&!r?mc(n):0;var o=e.length;return n<0&&(n=yn(o+n,0)),sc(e)?n<=o&&e.indexOf(t,n)>-1:!!o&&qt(e,t,n)>-1},Un.indexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var o=null==n?0:mc(n);return o<0&&(o=yn(r+o,0)),qt(e,t,o)},Un.inRange=function(e,t,n){return t=hc(t),n===o?(n=t,t=0):n=hc(n),function(e,t,n){return e>=xn(t,n)&&e=-9007199254740991&&e<=h},Un.isSet=cc,Un.isString=sc,Un.isSymbol=lc,Un.isTypedArray=uc,Un.isUndefined=function(e){return e===o},Un.isWeakMap=function(e){return nc(e)&&gi(e)==R},Un.isWeakSet=function(e){return nc(e)&&"[object WeakSet]"==Dr(e)},Un.join=function(e,t){return null==e?"":zt.call(e,t)},Un.kebabCase=Vc,Un.last=Zi,Un.lastIndexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var i=r;return n!==o&&(i=(i=mc(n))<0?yn(r+i,0):xn(i,r-1)),t==t?function(e,t,n){for(var r=n+1;r--;)if(e[r]===t)return r;return r}(e,t,i):$t(e,Vt,i,!0)},Un.lowerCase=Wc,Un.lowerFirst=Gc,Un.lt=dc,Un.lte=fc,Un.max=function(e){return e&&e.length?gr(e,os,Ar):o},Un.maxBy=function(e,t){return e&&e.length?gr(e,ui(t,2),Ar):o},Un.mean=function(e){return Wt(e,os)},Un.meanBy=function(e,t){return Wt(e,ui(t,2))},Un.min=function(e){return e&&e.length?gr(e,os,Fr):o},Un.minBy=function(e,t){return e&&e.length?gr(e,ui(t,2),Fr):o},Un.stubArray=gs,Un.stubFalse=bs,Un.stubObject=function(){return{}},Un.stubString=function(){return""},Un.stubTrue=function(){return!0},Un.multiply=_s,Un.nth=function(e,t){return e&&e.length?Hr(e,mc(t)):o},Un.noConflict=function(){return mt._===this&&(mt._=Ue),this},Un.noop=ls,Un.now=Aa,Un.pad=function(e,t,n){e=yc(e);var r=(t=mc(t))?hn(e):0;if(!t||r>=t)return e;var o=(t-r)/2;return Wo(gt(o),n)+e+Wo(ht(o),n)},Un.padEnd=function(e,t,n){e=yc(e);var r=(t=mc(t))?hn(e):0;return t&&rt){var r=e;e=t,t=r}if(n||e%1||t%1){var i=_n();return xn(e+i*(t-e+dt("1e-"+((i+"").length-1))),t)}return Yr(e,t)},Un.reduce=function(e,t,n){var r=Va(e)?Mt:Yt,o=arguments.length<3;return r(e,ui(t,4),n,o,pr)},Un.reduceRight=function(e,t,n){var r=Va(e)?Bt:Yt,o=arguments.length<3;return r(e,ui(t,4),n,o,hr)},Un.repeat=function(e,t,n){return t=(n?wi(e,t,n):t===o)?1:mc(t),Qr(yc(e),t)},Un.replace=function(){var e=arguments,t=yc(e[0]);return e.length<3?t:t.replace(e[1],e[2])},Un.result=function(e,t,n){var r=-1,i=(t=wo(t,e)).length;for(i||(i=1,e=o);++rh)return[];var n=g,r=xn(e,g);t=ui(t),e-=g;for(var o=Zt(r,t);++n=a)return e;var s=n-hn(r);if(s<1)return r;var l=c?_o(c,0,s).join(""):e.slice(0,s);if(i===o)return l+r;if(c&&(s+=l.length-s),ac(i)){if(e.slice(s).search(i)){var u,d=l;for(i.global||(i=Oe(i.source,yc(me.exec(i))+"g")),i.lastIndex=0;u=i.exec(d);)var f=u.index;l=l.slice(0,f===o?s:f)}}else if(e.indexOf(uo(i),s)!=s){var p=l.lastIndexOf(i);p>-1&&(l=l.slice(0,p))}return l+r},Un.unescape=function(e){return(e=yc(e))&&Q.test(e)?e.replace(K,bn):e},Un.uniqueId=function(e){var t=++Me;return yc(e)+t},Un.upperCase=Qc,Un.upperFirst=Zc,Un.each=ya,Un.eachRight=xa,Un.first=Gi,ss(Un,(Ss={},wr(Un,(function(e,t){Le.call(Un.prototype,t)||(Ss[t]=e)})),Ss),{chain:!1}),Un.VERSION="4.17.21",Ct(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(e){Un[e].placeholder=Un})),Ct(["drop","take"],(function(e,t){Vn.prototype[e]=function(n){n=n===o?1:yn(mc(n),0);var r=this.__filtered__&&!t?new Vn(this):this.clone();return r.__filtered__?r.__takeCount__=xn(n,r.__takeCount__):r.__views__.push({size:xn(n,g),type:e+(r.__dir__<0?"Right":"")}),r},Vn.prototype[e+"Right"]=function(t){return this.reverse()[e](t).reverse()}})),Ct(["filter","map","takeWhile"],(function(e,t){var n=t+1,r=1==n||3==n;Vn.prototype[e]=function(e){var t=this.clone();return t.__iteratees__.push({iteratee:ui(e,3),type:n}),t.__filtered__=t.__filtered__||r,t}})),Ct(["head","last"],(function(e,t){var n="take"+(t?"Right":"");Vn.prototype[e]=function(){return this[n](1).value()[0]}})),Ct(["initial","tail"],(function(e,t){var n="drop"+(t?"":"Right");Vn.prototype[e]=function(){return this.__filtered__?new Vn(this):this[n](1)}})),Vn.prototype.compact=function(){return this.filter(os)},Vn.prototype.find=function(e){return this.filter(e).head()},Vn.prototype.findLast=function(e){return this.reverse().find(e)},Vn.prototype.invokeMap=Zr((function(e,t){return"function"==typeof e?new Vn(this):this.map((function(n){return Pr(n,e,t)}))})),Vn.prototype.reject=function(e){return this.filter(La(ui(e)))},Vn.prototype.slice=function(e,t){e=mc(e);var n=this;return n.__filtered__&&(e>0||t<0)?new Vn(n):(e<0?n=n.takeRight(-e):e&&(n=n.drop(e)),t!==o&&(n=(t=mc(t))<0?n.dropRight(-t):n.take(t-e)),n)},Vn.prototype.takeRightWhile=function(e){return this.reverse().takeWhile(e).reverse()},Vn.prototype.toArray=function(){return this.take(g)},wr(Vn.prototype,(function(e,t){var n=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),i=Un[r?"take"+("last"==t?"Right":""):t],a=r||/^find/.test(t);i&&(Un.prototype[t]=function(){var t=this.__wrapped__,c=r?[1]:arguments,s=t instanceof Vn,l=c[0],u=s||Va(t),d=function(e){var t=i.apply(Un,Lt([e],c));return r&&f?t[0]:t};u&&n&&"function"==typeof l&&1!=l.length&&(s=u=!1);var f=this.__chain__,p=!!this.__actions__.length,h=a&&!f,m=s&&!p;if(!a&&u){t=m?t:new Vn(this);var g=e.apply(t,c);return g.__actions__.push({func:ha,args:[d],thisArg:o}),new Hn(g,f)}return h&&m?e.apply(this,c):(g=this.thru(d),h?r?g.value()[0]:g.value():g)})})),Ct(["pop","push","shift","sort","splice","unshift"],(function(e){var t=Pe[e],n=/^(?:push|sort|unshift)$/.test(e)?"tap":"thru",r=/^(?:pop|shift)$/.test(e);Un.prototype[e]=function(){var e=arguments;if(r&&!this.__chain__){var o=this.value();return t.apply(Va(o)?o:[],e)}return this[n]((function(n){return t.apply(Va(n)?n:[],e)}))}})),wr(Vn.prototype,(function(e,t){var n=Un[t];if(n){var r=n.name+"";Le.call(In,r)||(In[r]=[]),In[r].push({name:t,func:n})}})),In[$o(o,2).name]=[{name:"wrapper",func:o}],Vn.prototype.clone=function(){var e=new Vn(this.__wrapped__);return e.__actions__=Po(this.__actions__),e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=Po(this.__iteratees__),e.__takeCount__=this.__takeCount__,e.__views__=Po(this.__views__),e},Vn.prototype.reverse=function(){if(this.__filtered__){var e=new Vn(this);e.__dir__=-1,e.__filtered__=!0}else(e=this.clone()).__dir__*=-1;return e},Vn.prototype.value=function(){var e=this.__wrapped__.value(),t=this.__dir__,n=Va(e),r=t<0,o=n?e.length:0,i=function(e,t,n){var r=-1,o=n.length;for(;++r=this.__values__.length;return{done:e,value:e?o:this.__values__[this.__index__++]}},Un.prototype.plant=function(e){for(var t,n=this;n instanceof qn;){var r=zi(n);r.__index__=0,r.__values__=o,t?i.__wrapped__=r:t=r;var i=r;n=n.__wrapped__}return i.__wrapped__=e,t},Un.prototype.reverse=function(){var e=this.__wrapped__;if(e instanceof Vn){var t=e;return this.__actions__.length&&(t=new Vn(this)),(t=t.reverse()).__actions__.push({func:ha,args:[ta],thisArg:o}),new Hn(t,this.__chain__)}return this.thru(ta)},Un.prototype.toJSON=Un.prototype.valueOf=Un.prototype.value=function(){return go(this.__wrapped__,this.__actions__)},Un.prototype.first=Un.prototype.head,Xe&&(Un.prototype[Xe]=function(){return this}),Un}();mt._=vn,(r=function(){return vn}.call(t,n,t,e))===o||(e.exports=r)}.call(this)},50895:(e,t,n)=>{"use strict";n.r(t)},10119:(e,t,n)=>{"use strict";n.r(t)},5947:function(e,t,n){var r,o;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};function o(e,t,n){return en?n:e}function i(e){return 100*(-1+e)}function a(e,t,n){var o;return(o="translate3d"===r.positionUsing?{transform:"translate3d("+i(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+i(e)+"%,0)"}:{"margin-left":i(e)+"%"}).transition="all "+t+"ms "+n,o}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=o(e,r.minimum,1),n.status=1===e?null:e;var i=n.render(!t),l=i.querySelector(r.barSelector),u=r.speed,d=r.easing;return i.offsetWidth,c((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(l,a(e,u,d)),1===e?(s(i,{transition:"none",opacity:1}),i.offsetWidth,setTimeout((function(){s(i,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),u)}),u)):setTimeout(t,u)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*o(Math.random()*t,.1,.95)),t=o(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var o,a=t.querySelector(r.barSelector),c=e?"-100":i(n.status||0),l=document.querySelector(r.parent);return s(a,{transition:"all 0 linear",transform:"translate3d("+c+"%,0,0)"}),r.showSpinner||(o=t.querySelector(r.spinnerSelector))&&p(o),l!=document.body&&u(l,"nprogress-custom-parent"),l.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var c=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,o=e.length,i=t.charAt(0).toUpperCase()+t.slice(1);o--;)if((r=e[o]+i)in n)return r;return t}function o(e){return e=n(e),t[e]||(t[e]=r(e))}function i(e,t,n){t=o(t),e.style[t]=n}return function(e,t){var n,r,o=arguments;if(2==o.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&i(e,n,r);else i(e,o[1],o[2])}}();function l(e,t){return("string"==typeof e?e:f(e)).indexOf(" "+t+" ")>=0}function u(e,t){var n=f(e),r=n+t;l(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=f(e);l(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function f(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(o="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=o)},45228:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(o){return!1}}()?Object.assign:function(e,o){for(var i,a,c=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),s=1;s{"use strict";n.d(t,{A:()=>i});var r=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof o?new o(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=d.reach);_+=S.value.length,S=S.next){var E=S.value;if(t.length>e.length)return;if(!(E instanceof o)){var k,D=1;if(v){if(!(k=i(w,_,e,b))||k.index>=e.length)break;var A=k.index,O=k.index+k[0].length,C=_;for(C+=S.value.length;A>=C;)C+=(S=S.next).value.length;if(_=C-=S.value.length,S.value instanceof o)continue;for(var T=S;T!==t.tail&&(Cd.reach&&(d.reach=N);var j=S.prev;if(I&&(j=s(t,j,I),_+=I.length),l(t,j,D),S=s(t,j,new o(f,g?r.tokenize(P,g):P,y,P)),R&&s(t,S,R),D>1){var L={cause:f+","+h,reach:N};a(e,t,n,S.prev,_,L),d&&L.reach>d.reach&&(d.reach=L.reach)}}}}}}function c(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,o={value:n,prev:t,next:r};return t.next=o,r.prev=o,e.length++,o}function l(e,t,n){for(var r=t.next,o=0;o"+i.content+""},r}(),o=r;r.default=r,o.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},o.languages.markup.tag.inside["attr-value"].inside.entity=o.languages.markup.entity,o.languages.markup.doctype.inside["internal-subset"].inside=o.languages.markup,o.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(o.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:o.languages[t]},n.cdata=/^$/i;var r={"included-cdata":{pattern://i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:o.languages[t]};var i={};i[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:r},o.languages.insertBefore("markup","cdata",i)}}),Object.defineProperty(o.languages.markup.tag,"addAttribute",{value:function(e,t){o.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:o.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),o.languages.html=o.languages.markup,o.languages.mathml=o.languages.markup,o.languages.svg=o.languages.markup,o.languages.xml=o.languages.extend("markup",{}),o.languages.ssml=o.languages.xml,o.languages.atom=o.languages.xml,o.languages.rss=o.languages.xml,function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var o=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],i=r.variable[1].inside,a=0;a]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},o.languages.c=o.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),o.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),o.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},o.languages.c.string],char:o.languages.c.char,comment:o.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:o.languages.c}}}}),o.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete o.languages.c.boolean,function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(o),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(o),function(e){var t,n=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+n.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[n,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}});var r={pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0},o={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0};e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:r,number:o,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:r,number:o})}(o),o.languages.javascript=o.languages.extend("clike",{"class-name":[o.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),o.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,o.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:o.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:o.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:o.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:o.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:o.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),o.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:o.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),o.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),o.languages.markup&&(o.languages.markup.tag.addInlined("script","javascript"),o.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),o.languages.js=o.languages.javascript,function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(o),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",o=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),i=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function a(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return"(?:"+o+"|"+i+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:a(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:a(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:a(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:a(i),lookbehind:!0,greedy:!0},number:{pattern:a(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(o),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(//g,(function(){return t})),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,o=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,(function(){return r})),i=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+o+i+"(?:"+o+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+o+i+")(?:"+o+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+o+")"+i+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+o+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~))+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(t){["url","bold","italic","strike","code-snippet"].forEach((function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])}))})),e.hooks.add("after-tokenize",(function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(o),o.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:o.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},o.hooks.add("after-tokenize",(function(e){if("graphql"===e.language)for(var t=e.tokens.filter((function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type})),n=0;n0)){var c=f(/^\{$/,/^\}$/);if(-1===c)continue;for(var s=n;s=0&&p(l,"variable-input")}}}}function u(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,o=r.inside["interpolation-punctuation"],i=r.pattern.source;function a(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function c(e,t){return"___"+t.toUpperCase()+"_"+e+"___"}function s(t,n,r){var o={code:t,grammar:n,language:r};return e.hooks.run("before-tokenize",o),o.tokens=e.tokenize(o.code,o.grammar),e.hooks.run("after-tokenize",o),o.tokens}function l(t){var n={};n["interpolation-punctuation"]=o;var i=e.tokenize(t,n);if(3===i.length){var a=[1,1];a.push.apply(a,s(i[1],e.languages.javascript,"javascript")),i.splice.apply(i,a)}return new e.Token("interpolation",i,r.alias,t)}function u(t,n,r){var o=e.tokenize(t,{interpolation:{pattern:RegExp(i),lookbehind:!0}}),a=0,u={},d=s(o.map((function(e){if("string"==typeof e)return e;for(var n,o=e.content;-1!==t.indexOf(n=c(a++,r)););return u[n]=o,n})).join(""),n,r),f=Object.keys(u);return a=0,function e(t){for(var n=0;n=f.length)return;var r=t[n];if("string"==typeof r||"string"==typeof r.content){var o=f[a],i="string"==typeof r?r:r.content,c=i.indexOf(o);if(-1!==c){++a;var s=i.substring(0,c),d=l(u[o]),p=i.substring(c+o.length),h=[];if(s&&h.push(s),h.push(d),p){var m=[p];e(m),h.push.apply(h,m)}"string"==typeof r?(t.splice.apply(t,[n,1].concat(h)),n+=h.length-1):r.content=h}}else{var g=r.content;Array.isArray(g)?e(g):e([g])}}}(d),new e.Token(r,d,"language-"+r,t)}e.languages.javascript["template-string"]=[a("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),a("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),a("svg",/\bsvg/.source),a("markdown",/\b(?:markdown|md)/.source),a("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),a("sql",/\bsql/.source),t].filter(Boolean);var d={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function f(e){return"string"==typeof e?e:Array.isArray(e)?e.map(f).join(""):f(e.content)}e.hooks.add("after-tokenize",(function(t){t.language in d&&function t(n){for(var r=0,o=n.length;r]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(o),function(e){function t(e,t){return RegExp(e.replace(//g,(function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source})),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:(?:\s*,\s*(?:\*\s*as\s+|\{[^{}]*\}))?|\*\s*as\s+|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r*\.{3}(?:[^{}]|)*\})/.source;function i(e,t){return e=e.replace(//g,(function(){return n})).replace(//g,(function(){return r})).replace(//g,(function(){return o})),RegExp(e,t)}o=i(o).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=i(/<\/?(?:[\w.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|))?|))**\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:i(//.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:i(/=/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var a=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(a).join(""):""},c=function(t){for(var n=[],r=0;r0&&n[n.length-1].tagName===a(o.content[0].content[1])&&n.pop():"/>"===o.content[o.content.length-1].content||n.push({tagName:a(o.content[0].content[1]),openedBraces:0}):n.length>0&&"punctuation"===o.type&&"{"===o.content?n[n.length-1].openedBraces++:n.length>0&&n[n.length-1].openedBraces>0&&"punctuation"===o.type&&"}"===o.content?n[n.length-1].openedBraces--:i=!0),(i||"string"==typeof o)&&n.length>0&&0===n[n.length-1].openedBraces){var s=a(o);r0&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(s=a(t[r-1])+s,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",s,null,s)}o.content&&"string"!=typeof o.content&&c(o.content)}};e.hooks.add("after-tokenize",(function(e){"jsx"!==e.language&&"tsx"!==e.language||c(e.tokens)}))}(o),function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var t={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(t).forEach((function(n){var r=t[n],o=[];/^\w+$/.test(n)||o.push(/\w+/.exec(n)[0]),"diff"===n&&o.push("bold"),e.languages.diff[n]={pattern:RegExp("^(?:["+r+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:o,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(n)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:t})}(o),o.languages.git={comment:/^#.*/m,deleted:/^[-\u2013].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m},o.languages.go=o.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),o.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete o.languages.go["class-name"],function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,o,i){if(n.language===r){var a=n.tokenStack=[];n.code=n.code.replace(o,(function(e){if("function"==typeof i&&!i(e))return e;for(var o,c=a.length;-1!==n.code.indexOf(o=t(r,c));)++c;return a[c]=e,o})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var o=0,i=Object.keys(n.tokenStack);!function a(c){for(var s=0;s=i.length);s++){var l=c[s];if("string"==typeof l||l.content&&"string"==typeof l.content){var u=i[o],d=n.tokenStack[u],f="string"==typeof l?l:l.content,p=t(r,u),h=f.indexOf(p);if(h>-1){++o;var m=f.substring(0,h),g=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),b=f.substring(h+p.length),v=[];m&&v.push.apply(v,a([m])),v.push(g),b&&v.push.apply(v,a([b])),"string"==typeof l?c.splice.apply(c,[s,1].concat(v)):l.content=v}}else l.content&&a(l.content)}return c}(n.tokens)}}}})}(o),function(e){e.languages.handlebars={comment:/\{\{![\s\S]*?\}\}/,delimiter:{pattern:/^\{\{\{?|\}\}\}?$/,alias:"punctuation"},string:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][+-]?\d+)?/,boolean:/\b(?:false|true)\b/,block:{pattern:/^(\s*(?:~\s*)?)[#\/]\S+?(?=\s*(?:~\s*)?$|\s)/,lookbehind:!0,alias:"keyword"},brackets:{pattern:/\[[^\]]+\]/,inside:{punctuation:/\[|\]/,variable:/[\s\S]+/}},punctuation:/[!"#%&':()*+,.\/;<=>@\[\\\]^`{|}~]/,variable:/[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/},e.hooks.add("before-tokenize",(function(t){e.languages["markup-templating"].buildPlaceholders(t,"handlebars",/\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"handlebars")})),e.languages.hbs=e.languages.handlebars}(o),o.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},o.languages.webmanifest=o.languages.json,o.languages.less=o.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-](?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/,operator:/[+\-*\/]/}),o.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-].*?(?=[(;])/,lookbehind:!0,alias:"function"}}),o.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"builtin-target":{pattern:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,alias:"builtin"},target:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,alias:"symbol",inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,function:{pattern:/(\()(?:abspath|addsuffix|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:list|s)?)(?=[ \t])/,lookbehind:!0},operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/},o.languages.objectivec=o.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete o.languages.objectivec["class-name"],o.languages.objc=o.languages.objectivec,o.languages.ocaml={comment:{pattern:/\(\*[\s\S]*?\*\)/,greedy:!0},char:{pattern:/'(?:[^\\\r\n']|\\(?:.|[ox]?[0-9a-f]{1,3}))'/i,greedy:!0},string:[{pattern:/"(?:\\(?:[\s\S]|\r\n)|[^\\\r\n"])*"/,greedy:!0},{pattern:/\{([a-z_]*)\|[\s\S]*?\|\1\}/,greedy:!0}],number:[/\b(?:0b[01][01_]*|0o[0-7][0-7_]*)\b/i,/\b0x[a-f0-9][a-f0-9_]*(?:\.[a-f0-9_]*)?(?:p[+-]?\d[\d_]*)?(?!\w)/i,/\b\d[\d_]*(?:\.[\d_]*)?(?:e[+-]?\d[\d_]*)?(?!\w)/i],directive:{pattern:/\B#\w+/,alias:"property"},label:{pattern:/\B~\w+/,alias:"property"},"type-variable":{pattern:/\B'\w+/,alias:"function"},variant:{pattern:/`\w+/,alias:"symbol"},keyword:/\b(?:as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|value|virtual|when|where|while|with)\b/,boolean:/\b(?:false|true)\b/,"operator-like-punctuation":{pattern:/\[[<>|]|[>|]\]|\{<|>\}/,alias:"punctuation"},operator:/\.[.~]|:[=>]|[=<>@^|&+\-*\/$%!?~][!$%&*+\-.\/:<=>?@^|~]*|\b(?:and|asr|land|lor|lsl|lsr|lxor|mod|or)\b/,punctuation:/;;|::|[(){}\[\].,:;#]|\b_\b/},o.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},o.languages.python["string-interpolation"].inside.interpolation.inside.rest=o.languages.python,o.languages.py=o.languages.python,o.languages.reason=o.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),o.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete o.languages.reason.function,function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t].+)*/m,lookbehind:!0,greedy:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,greedy:!0,inside:{atrule:/(?:@[\w-]+|[+=])/}}}),delete e.languages.sass.atrule;var t=/\$[-\w]+|#\{\$[-\w]+\}/,n=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|not|or)\b/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,greedy:!0,inside:{punctuation:/:/,variable:t,operator:n}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s].*)/m,greedy:!0,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:t,operator:n,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/^([ \t]*)\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*)*/m,lookbehind:!0,greedy:!0}})}(o),o.languages.scss=o.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-](?:\([^()]+\)|[^()\s]|\s+(?!\s))*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()\s]|\s+(?!\s)|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}][^:{}]*[:{][^}]))/,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[-\w]|\$[-\w]|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),o.languages.insertBefore("scss","atrule",{keyword:[/@(?:content|debug|each|else(?: if)?|extend|for|forward|function|if|import|include|mixin|return|use|warn|while)\b/i,{pattern:/( )(?:from|through)(?= )/,lookbehind:!0}]}),o.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),o.languages.insertBefore("scss","function",{"module-modifier":{pattern:/\b(?:as|hide|show|with)\b/i,alias:"keyword"},placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|not|or)(?=\s)/,lookbehind:!0}}),o.languages.scss.atrule.inside.rest=o.languages.scss,function(e){var t={pattern:/(\b\d+)(?:%|[a-z]+)/,lookbehind:!0},n={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},r={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},url:{pattern:/\burl\((["']?).*?\1\)/i,greedy:!0},string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:else|for|if|return|unless)(?=\s|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,color:[/\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,boolean:/\b(?:false|true)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.{2,3}|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],number:n,punctuation:/[{}()\[\];:,]/};r.interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^\{|\}$/,alias:"punctuation"},rest:r}},r.func={pattern:/[\w-]+\([^)]*\).*/,inside:{function:/^[^(]+/,rest:r}},e.languages.stylus={"atrule-declaration":{pattern:/(^[ \t]*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:r}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:\{[^{}]*\}|\S.*|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:r}},statement:{pattern:/(^[ \t]*)(?:else|for|if|return|unless)[ \t].+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:r}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)(?!\s)[^{\r\n]*(?:;|[^{\r\n,]$(?!(?:\r?\n|\r)(?:\{|\2[ \t])))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:r.interpolation}},rest:r}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t])))/m,lookbehind:!0,inside:{interpolation:r.interpolation,comment:r.comment,punctuation:/[{},]/}},func:r.func,string:r.string,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0,greedy:!0},interpolation:r.interpolation,punctuation:/[{}()\[\];:.]/}}(o),function(e){var t=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"];var n=e.languages.tsx.tag;n.pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+n.pattern.source+")",n.pattern.flags),n.lookbehind=!0}(o),o.languages.wasm={comment:[/\(;[\s\S]*?;\)/,{pattern:/;;.*/,greedy:!0}],string:{pattern:/"(?:\\[\s\S]|[^"\\])*"/,greedy:!0},keyword:[{pattern:/\b(?:align|offset)=/,inside:{operator:/=/}},{pattern:/\b(?:(?:f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|neg?|nearest|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|sqrt|store(?:8|16|32)?|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))?|memory\.(?:grow|size))\b/,inside:{punctuation:/\./}},/\b(?:anyfunc|block|br(?:_if|_table)?|call(?:_indirect)?|data|drop|elem|else|end|export|func|get_(?:global|local)|global|if|import|local|loop|memory|module|mut|nop|offset|param|result|return|select|set_(?:global|local)|start|table|tee_local|then|type|unreachable)\b/],variable:/\$[\w!#$%&'*+\-./:<=>?@\\^`|~]+/,number:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/,punctuation:/[()]/};const i=o},43523:()=>{!function(e){var t={pattern:/((?:^|[^\\$])(?:\\{2})*)\$(?:\w+|\{[^{}]*\})/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:null}}};e.languages.gradle=e.languages.extend("clike",{string:{pattern:/'''(?:[^\\]|\\[\s\S])*?'''|'(?:\\.|[^\\'\r\n])*'/,greedy:!0},keyword:/\b(?:apply|def|dependencies|else|if|implementation|import|plugin|plugins|project|repositories|repository|sourceSets|tasks|val)\b/,number:/\b(?:0b[01_]+|0x[\da-f_]+(?:\.[\da-f_p\-]+)?|[\d_]+(?:\.[\d_]+)?(?:e[+-]?\d+)?)[glidf]?\b/i,operator:{pattern:/(^|[^.])(?:~|==?~?|\?[.:]?|\*(?:[.=]|\*=?)?|\.[@&]|\.\.<|\.\.(?!\.)|-[-=>]?|\+[+=]?|!=?|<(?:<=?|=>?)?|>(?:>>?=?|=)?|&[&=]?|\|[|=]?|\/=?|\^=?|%=?)/,lookbehind:!0},punctuation:/\.+|[{}[\];(),:$]/}),e.languages.insertBefore("gradle","string",{shebang:{pattern:/#!.+/,alias:"comment",greedy:!0},"interpolation-string":{pattern:/"""(?:[^\\]|\\[\s\S])*?"""|(["/])(?:\\.|(?!\1)[^\\\r\n])*\1|\$\/(?:[^/$]|\$(?:[/$]|(?![/$]))|\/(?!\$))*\/\$/,greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}}}),e.languages.insertBefore("gradle","punctuation",{"spock-block":/\b(?:and|cleanup|expect|given|setup|then|when|where):/}),e.languages.insertBefore("gradle","function",{annotation:{pattern:/(^|[^.])@\w+/,lookbehind:!0,alias:"punctuation"}}),t.inside.expression.inside=e.languages.gradle}(Prism)},70824:()=>{Prism.languages.ini={comment:{pattern:/(^[ \f\t\v]*)[#;][^\n\r]*/m,lookbehind:!0},section:{pattern:/(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,lookbehind:!0,inside:{"section-name":{pattern:/(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,lookbehind:!0,alias:"selector"},punctuation:/\[|\]/}},key:{pattern:/(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,lookbehind:!0,alias:"attr-name"},value:{pattern:/(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,lookbehind:!0,alias:"attr-value",inside:{"inner-value":{pattern:/^("|').+(?=\1$)/,lookbehind:!0}}},punctuation:/=/}},96976:()=>{!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,r={pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[r,{pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:r.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+n+/[A-Z]\w*\b/.source),lookbehind:!0,inside:r.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":r,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+n+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:r.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+n+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:r.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism)},20867:(e,t,n)=>{var r={"./prism-gradle":43523,"./prism-ini":70824,"./prism-java":96976};function o(e){var t=i(e);return n(t)}function i(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}o.keys=function(){return Object.keys(r)},o.resolve=i,e.exports=o,o.id=20867},2694:(e,t,n)=>{"use strict";var r=n(6925);function o(){}function i(){}i.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,i,a){if(a!==r){var c=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw c.name="Invariant Violation",c}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:o};return n.PropTypes=n,n}},5556:(e,t,n)=>{e.exports=n(2694)()},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},22551:(e,t,n)=>{"use strict";var r=n(96540),o=n(45228),i=n(69982);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n