codeceptjs 3.4.1 → 3.5.1-2.beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/README.md +31 -30
  2. package/bin/codecept.js +1 -1
  3. package/lib/actor.js +6 -3
  4. package/lib/ai.js +180 -0
  5. package/lib/cli.js +13 -3
  6. package/lib/codecept.js +8 -0
  7. package/lib/colorUtils.js +10 -0
  8. package/lib/command/definitions.js +2 -7
  9. package/lib/command/dryRun.js +11 -2
  10. package/lib/command/generate.js +46 -3
  11. package/lib/command/info.js +24 -0
  12. package/lib/command/init.js +64 -6
  13. package/lib/command/interactive.js +15 -1
  14. package/lib/command/run-multiple/collection.js +17 -5
  15. package/lib/command/run-multiple.js +4 -2
  16. package/lib/command/run-workers.js +68 -5
  17. package/lib/command/run.js +7 -0
  18. package/lib/command/workers/runTests.js +39 -0
  19. package/lib/container.js +13 -3
  20. package/lib/data/context.js +14 -6
  21. package/lib/event.js +4 -0
  22. package/lib/helper/ApiDataFactory.js +2 -1
  23. package/lib/helper/Appium.js +116 -29
  24. package/lib/helper/Expect.js +422 -0
  25. package/lib/helper/FileSystem.js +1 -1
  26. package/lib/helper/GraphQL.js +25 -0
  27. package/lib/helper/JSONResponse.js +4 -4
  28. package/lib/helper/Nightmare.js +10 -5
  29. package/lib/helper/OpenAI.js +126 -0
  30. package/lib/helper/Playwright.js +1298 -229
  31. package/lib/helper/Protractor.js +12 -7
  32. package/lib/helper/Puppeteer.js +204 -64
  33. package/lib/helper/REST.js +15 -5
  34. package/lib/helper/TestCafe.js +45 -10
  35. package/lib/helper/WebDriver.js +252 -83
  36. package/lib/helper/errors/ElementNotFound.js +2 -1
  37. package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
  38. package/lib/helper/scripts/blurElement.js +17 -0
  39. package/lib/helper/scripts/focusElement.js +17 -0
  40. package/lib/helper/scripts/highlightElement.js +20 -0
  41. package/lib/html.js +258 -0
  42. package/lib/interfaces/bdd.js +1 -1
  43. package/lib/interfaces/gherkin.js +37 -3
  44. package/lib/interfaces/scenarioConfig.js +1 -0
  45. package/lib/listener/retry.js +2 -1
  46. package/lib/locator.js +17 -4
  47. package/lib/mochaFactory.js +2 -1
  48. package/lib/output.js +1 -1
  49. package/lib/pause.js +78 -19
  50. package/lib/plugin/autoLogin.js +45 -10
  51. package/lib/plugin/debugErrors.js +67 -0
  52. package/lib/plugin/fakerTransform.js +4 -6
  53. package/lib/plugin/heal.js +209 -0
  54. package/lib/plugin/retryFailedStep.js +10 -1
  55. package/lib/plugin/retryTo.js +2 -4
  56. package/lib/plugin/screenshotOnFail.js +11 -2
  57. package/lib/plugin/selenoid.js +6 -1
  58. package/lib/plugin/standardActingHelpers.js +0 -2
  59. package/lib/plugin/stepByStepReport.js +2 -2
  60. package/lib/plugin/tryTo.js +5 -7
  61. package/lib/plugin/wdio.js +0 -1
  62. package/lib/recorder.js +22 -11
  63. package/lib/secret.js +5 -4
  64. package/lib/session.js +1 -1
  65. package/lib/step.js +36 -12
  66. package/lib/ui.js +5 -3
  67. package/lib/utils.js +22 -1
  68. package/lib/workers.js +83 -10
  69. package/package.json +117 -95
  70. package/translations/de-DE.js +5 -0
  71. package/translations/fr-FR.js +14 -1
  72. package/translations/it-IT.js +1 -0
  73. package/translations/ja-JP.js +14 -9
  74. package/translations/pl-PL.js +5 -0
  75. package/translations/pt-BR.js +1 -0
  76. package/translations/ru-RU.js +1 -0
  77. package/translations/zh-CN.js +5 -0
  78. package/translations/zh-TW.js +5 -0
  79. package/typings/index.d.ts +51 -15
  80. package/typings/promiseBasedTypes.d.ts +864 -802
  81. package/typings/types.d.ts +1339 -744
  82. package/CHANGELOG.md +0 -2427
  83. package/docs/advanced.md +0 -351
  84. package/docs/api.md +0 -323
  85. package/docs/basics.md +0 -980
  86. package/docs/bdd.md +0 -535
  87. package/docs/best.md +0 -237
  88. package/docs/books.md +0 -37
  89. package/docs/bootstrap.md +0 -135
  90. package/docs/build/ApiDataFactory.js +0 -409
  91. package/docs/build/Appium.js +0 -1938
  92. package/docs/build/FileSystem.js +0 -228
  93. package/docs/build/GraphQL.js +0 -204
  94. package/docs/build/GraphQLDataFactory.js +0 -309
  95. package/docs/build/JSONResponse.js +0 -338
  96. package/docs/build/Mochawesome.js +0 -71
  97. package/docs/build/Nightmare.js +0 -2145
  98. package/docs/build/Playwright.js +0 -3986
  99. package/docs/build/Polly.js +0 -42
  100. package/docs/build/Protractor.js +0 -2699
  101. package/docs/build/Puppeteer.js +0 -3710
  102. package/docs/build/REST.js +0 -334
  103. package/docs/build/SeleniumWebdriver.js +0 -76
  104. package/docs/build/TestCafe.js +0 -2057
  105. package/docs/build/WebDriver.js +0 -4017
  106. package/docs/changelog.md +0 -2436
  107. package/docs/commands.md +0 -254
  108. package/docs/community-helpers.md +0 -58
  109. package/docs/configuration.md +0 -157
  110. package/docs/continuous-integration.md +0 -22
  111. package/docs/custom-helpers.md +0 -306
  112. package/docs/data.md +0 -375
  113. package/docs/detox.md +0 -235
  114. package/docs/docker.md +0 -137
  115. package/docs/email.md +0 -183
  116. package/docs/examples.md +0 -149
  117. package/docs/helpers/ApiDataFactory.md +0 -266
  118. package/docs/helpers/Appium.md +0 -1312
  119. package/docs/helpers/Detox.md +0 -586
  120. package/docs/helpers/FileSystem.md +0 -152
  121. package/docs/helpers/GraphQL.md +0 -130
  122. package/docs/helpers/GraphQLDataFactory.md +0 -226
  123. package/docs/helpers/JSONResponse.md +0 -254
  124. package/docs/helpers/Mochawesome.md +0 -8
  125. package/docs/helpers/MockRequest.md +0 -377
  126. package/docs/helpers/Nightmare.md +0 -1256
  127. package/docs/helpers/Playwright.md +0 -2208
  128. package/docs/helpers/Polly.md +0 -44
  129. package/docs/helpers/Puppeteer-firefox.md +0 -86
  130. package/docs/helpers/Puppeteer.md +0 -2141
  131. package/docs/helpers/REST.md +0 -217
  132. package/docs/helpers/TestCafe.md +0 -1222
  133. package/docs/helpers/WebDriver.md +0 -2319
  134. package/docs/hooks.md +0 -340
  135. package/docs/index.md +0 -111
  136. package/docs/installation.md +0 -75
  137. package/docs/internal-api.md +0 -265
  138. package/docs/locators.md +0 -331
  139. package/docs/mobile-react-native-locators.md +0 -67
  140. package/docs/mobile.md +0 -297
  141. package/docs/nightmare.md +0 -223
  142. package/docs/pageobjects.md +0 -291
  143. package/docs/parallel.md +0 -232
  144. package/docs/playwright.md +0 -609
  145. package/docs/plugins.md +0 -1171
  146. package/docs/puppeteer.md +0 -316
  147. package/docs/quickstart.md +0 -163
  148. package/docs/react.md +0 -69
  149. package/docs/reports.md +0 -392
  150. package/docs/secrets.md +0 -30
  151. package/docs/shadow.md +0 -68
  152. package/docs/shared/keys.mustache +0 -31
  153. package/docs/shared/react.mustache +0 -1
  154. package/docs/testcafe.md +0 -174
  155. package/docs/translation.md +0 -247
  156. package/docs/tutorial.md +0 -271
  157. package/docs/typescript.md +0 -180
  158. package/docs/ui.md +0 -59
  159. package/docs/videos.md +0 -28
  160. package/docs/visual.md +0 -202
  161. package/docs/vue.md +0 -121
  162. package/docs/webapi/amOnPage.mustache +0 -11
  163. package/docs/webapi/appendField.mustache +0 -9
  164. package/docs/webapi/attachFile.mustache +0 -12
  165. package/docs/webapi/checkOption.mustache +0 -13
  166. package/docs/webapi/clearCookie.mustache +0 -10
  167. package/docs/webapi/clearField.mustache +0 -9
  168. package/docs/webapi/click.mustache +0 -25
  169. package/docs/webapi/clickLink.mustache +0 -8
  170. package/docs/webapi/closeCurrentTab.mustache +0 -7
  171. package/docs/webapi/closeOtherTabs.mustache +0 -8
  172. package/docs/webapi/dontSee.mustache +0 -11
  173. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  174. package/docs/webapi/dontSeeCookie.mustache +0 -8
  175. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  176. package/docs/webapi/dontSeeElement.mustache +0 -8
  177. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  178. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  179. package/docs/webapi/dontSeeInField.mustache +0 -11
  180. package/docs/webapi/dontSeeInSource.mustache +0 -8
  181. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  182. package/docs/webapi/doubleClick.mustache +0 -13
  183. package/docs/webapi/downloadFile.mustache +0 -12
  184. package/docs/webapi/dragAndDrop.mustache +0 -9
  185. package/docs/webapi/dragSlider.mustache +0 -11
  186. package/docs/webapi/executeAsyncScript.mustache +0 -24
  187. package/docs/webapi/executeScript.mustache +0 -26
  188. package/docs/webapi/fillField.mustache +0 -16
  189. package/docs/webapi/forceClick.mustache +0 -28
  190. package/docs/webapi/forceRightClick.mustache +0 -18
  191. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  192. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  193. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  194. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  195. package/docs/webapi/grabCookie.mustache +0 -11
  196. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  197. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  198. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  199. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  200. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  201. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  202. package/docs/webapi/grabGeoLocation.mustache +0 -8
  203. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  204. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  205. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  206. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  207. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  208. package/docs/webapi/grabPopupText.mustache +0 -5
  209. package/docs/webapi/grabSource.mustache +0 -8
  210. package/docs/webapi/grabTextFrom.mustache +0 -10
  211. package/docs/webapi/grabTextFromAll.mustache +0 -9
  212. package/docs/webapi/grabTitle.mustache +0 -8
  213. package/docs/webapi/grabValueFrom.mustache +0 -9
  214. package/docs/webapi/grabValueFromAll.mustache +0 -8
  215. package/docs/webapi/moveCursorTo.mustache +0 -12
  216. package/docs/webapi/openNewTab.mustache +0 -7
  217. package/docs/webapi/pressKey.mustache +0 -12
  218. package/docs/webapi/pressKeyDown.mustache +0 -12
  219. package/docs/webapi/pressKeyUp.mustache +0 -12
  220. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  221. package/docs/webapi/refreshPage.mustache +0 -6
  222. package/docs/webapi/resizeWindow.mustache +0 -6
  223. package/docs/webapi/rightClick.mustache +0 -14
  224. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  225. package/docs/webapi/saveScreenshot.mustache +0 -12
  226. package/docs/webapi/say.mustache +0 -10
  227. package/docs/webapi/scrollIntoView.mustache +0 -11
  228. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  229. package/docs/webapi/scrollPageToTop.mustache +0 -6
  230. package/docs/webapi/scrollTo.mustache +0 -12
  231. package/docs/webapi/see.mustache +0 -11
  232. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  233. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  234. package/docs/webapi/seeCookie.mustache +0 -8
  235. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  236. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  237. package/docs/webapi/seeElement.mustache +0 -8
  238. package/docs/webapi/seeElementInDOM.mustache +0 -8
  239. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  240. package/docs/webapi/seeInField.mustache +0 -12
  241. package/docs/webapi/seeInPopup.mustache +0 -8
  242. package/docs/webapi/seeInSource.mustache +0 -7
  243. package/docs/webapi/seeInTitle.mustache +0 -8
  244. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  245. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  246. package/docs/webapi/seeTextEquals.mustache +0 -9
  247. package/docs/webapi/seeTitleEquals.mustache +0 -8
  248. package/docs/webapi/selectOption.mustache +0 -21
  249. package/docs/webapi/setCookie.mustache +0 -16
  250. package/docs/webapi/setGeoLocation.mustache +0 -12
  251. package/docs/webapi/switchTo.mustache +0 -9
  252. package/docs/webapi/switchToNextTab.mustache +0 -10
  253. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  254. package/docs/webapi/type.mustache +0 -18
  255. package/docs/webapi/uncheckOption.mustache +0 -13
  256. package/docs/webapi/wait.mustache +0 -8
  257. package/docs/webapi/waitForClickable.mustache +0 -11
  258. package/docs/webapi/waitForDetached.mustache +0 -10
  259. package/docs/webapi/waitForElement.mustache +0 -11
  260. package/docs/webapi/waitForEnabled.mustache +0 -6
  261. package/docs/webapi/waitForFunction.mustache +0 -17
  262. package/docs/webapi/waitForInvisible.mustache +0 -10
  263. package/docs/webapi/waitForText.mustache +0 -13
  264. package/docs/webapi/waitForValue.mustache +0 -10
  265. package/docs/webapi/waitForVisible.mustache +0 -10
  266. package/docs/webapi/waitInUrl.mustache +0 -9
  267. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  268. package/docs/webapi/waitToHide.mustache +0 -10
  269. package/docs/webapi/waitUrlEquals.mustache +0 -10
  270. package/docs/webdriver.md +0 -657
  271. package/docs/wiki/Books-&-Posts.md +0 -27
  272. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -49
  273. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -29
  274. package/docs/wiki/Examples.md +0 -139
  275. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  276. package/docs/wiki/Home.md +0 -16
  277. package/docs/wiki/Release-Process.md +0 -24
  278. package/docs/wiki/Roadmap.md +0 -23
  279. package/docs/wiki/Tests.md +0 -1393
  280. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  281. package/docs/wiki/Videos.md +0 -19
@@ -1,1938 +0,0 @@
1
- let webdriverio;
2
-
3
- const fs = require('fs');
4
- const axios = require('axios').default;
5
-
6
- const Webdriver = require('./WebDriver');
7
- const AssertionFailedError = require('../assert/error');
8
- const { truth } = require('../assert/truth');
9
- const recorder = require('../recorder');
10
- const Locator = require('../locator');
11
- const ConnectionRefused = require('./errors/ConnectionRefused');
12
-
13
- const mobileRoot = '//*';
14
- const webRoot = 'body';
15
- const supportedPlatform = {
16
- android: 'Android',
17
- iOS: 'iOS',
18
- };
19
-
20
- /**
21
- * Appium helper extends [Webriver](http://codecept.io/helpers/WebDriver/) helper.
22
- * It supports all browser methods and also includes special methods for mobile apps testing.
23
- * You can use this helper to test Web on desktop and mobile devices and mobile apps.
24
- *
25
- * ## Appium Installation
26
- *
27
- * Appium is an open source test automation framework for use with native, hybrid and mobile web apps that implements the WebDriver protocol.
28
- * It allows you to run Selenium tests on mobile devices and also test native, hybrid and mobile web apps.
29
- *
30
- * Download and install [Appium](http://appium.io/)
31
- *
32
- * ```sh
33
- * npm install -g appium
34
- * ```
35
- *
36
- * Launch the daemon: `appium`
37
- *
38
- * ## Helper configuration
39
- *
40
- * This helper should be configured in codecept.conf.ts or codecept.conf.js
41
- *
42
- * * `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage
43
- * * `host`: (default: 'localhost') Appium host
44
- * * `port`: (default: '4723') Appium port
45
- * * `platform`: (Android or IOS), which mobile OS to use; alias to desiredCapabilities.platformName
46
- * * `restart`: restart browser or app between tests (default: true), if set to false cookies will be cleaned but browser window will be kept and for apps nothing will be changed.
47
- * * `desiredCapabilities`: [], Appium capabilities, see below
48
- * * `platformName` - Which mobile OS platform to use
49
- * * `appPackage` - Java package of the Android app you want to run
50
- * * `appActivity` - Activity name for the Android activity you want to launch from your package.
51
- * * `deviceName`: The kind of mobile device or emulator to use
52
- * * `platformVersion`: Mobile OS version
53
- * * `app` - The absolute local path or remote http URL to an .ipa or .apk file, or a .zip containing one of these. Appium will attempt to install this app binary on the appropriate device first.
54
- * * `browserName`: Name of mobile web browser to automate. Should be an empty string if automating an app instead.
55
- *
56
- * Example Android App:
57
- *
58
- * ```js
59
- * {
60
- * helpers: {
61
- * Appium: {
62
- * platform: "Android",
63
- * desiredCapabilities: {
64
- * appPackage: "com.example.android.myApp",
65
- * appActivity: "MainActivity",
66
- * deviceName: "OnePlus3",
67
- * platformVersion: "6.0.1"
68
- * }
69
- * }
70
- * }
71
- * }
72
- * ```
73
- *
74
- * Example iOS Mobile Web with local Appium:
75
- *
76
- * ```js
77
- * {
78
- * helpers: {
79
- * Appium: {
80
- * platform: "iOS",
81
- * url: "https://the-internet.herokuapp.com/",
82
- * desiredCapabilities: {
83
- * deviceName: "iPhone X",
84
- * platformVersion: "12.0",
85
- * browserName: "safari"
86
- * }
87
- * }
88
- * }
89
- * }
90
- * ```
91
- *
92
- * Example iOS Mobile Web on BrowserStack:
93
- *
94
- * ```js
95
- * {
96
- * helpers: {
97
- * Appium: {
98
- * host: "hub-cloud.browserstack.com",
99
- * port: 4444,
100
- * user: process.env.BROWSERSTACK_USER,
101
- * key: process.env.BROWSERSTACK_KEY,
102
- * platform: "iOS",
103
- * url: "https://the-internet.herokuapp.com/",
104
- * desiredCapabilities: {
105
- * realMobile: "true",
106
- * device: "iPhone 8",
107
- * os_version: "12",
108
- * browserName: "safari"
109
- * }
110
- * }
111
- * }
112
- * }
113
- * ```
114
- *
115
- * Additional configuration params can be used from <https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md>
116
- *
117
- * ## Access From Helpers
118
- *
119
- * Receive a Appium client from a custom helper by accessing `browser` property:
120
- *
121
- * ```js
122
- * let browser = this.helpers['Appium'].browser
123
- * ```
124
- *
125
- * ## Methods
126
- */
127
- class Appium extends Webdriver {
128
- /**
129
- * Appium Special Methods for Mobile only
130
- * @augments WebDriver
131
- */
132
-
133
- // @ts-ignore
134
- constructor(config) {
135
- super(config);
136
-
137
- this.isRunning = false;
138
- this.axios = axios.create();
139
-
140
- webdriverio = require('webdriverio');
141
- }
142
-
143
- _validateConfig(config) {
144
- if (!(config.app || config.platform) && !config.browser) {
145
- throw new Error(`
146
- Appium requires either platform and app or a browser to be set.
147
- Check your codeceptjs config file to ensure these are set properly
148
- {
149
- "helpers": {
150
- "Appium": {
151
- "app": "/path/to/app/package"
152
- "platform": "MOBILE_OS",
153
- }
154
- }
155
- }
156
- `);
157
- }
158
-
159
- // set defaults
160
- const defaults = {
161
- // webdriverio defaults
162
- protocol: 'http',
163
- hostname: '0.0.0.0', // webdriverio specs
164
- port: 4723,
165
- path: '/wd/hub',
166
-
167
- // config
168
- waitForTimeout: 1000, // ms
169
- logLevel: 'error',
170
- capabilities: {},
171
- deprecationWarnings: false,
172
- restart: true,
173
- manualStart: false,
174
- timeouts: {
175
- script: 0, // ms
176
- },
177
- };
178
-
179
- // override defaults with config
180
- config = Object.assign(defaults, config);
181
-
182
- config.baseUrl = config.url || config.baseUrl;
183
- if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
184
- config.capabilities = config.desiredCapabilities;
185
- }
186
-
187
- config.capabilities.deviceName = config.device || config.capabilities.deviceName;
188
- config.capabilities.browserName = config.browser || config.capabilities.browserName;
189
- config.capabilities.app = config.app || config.capabilities.app;
190
- config.capabilities.platformName = config.platform || config.capabilities.platformName;
191
- config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel
192
- config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds
193
-
194
- // [CodeceptJS compatible] transform host to hostname
195
- config.hostname = config.host || config.hostname;
196
-
197
- if (!config.app && config.capabilities.browserName) {
198
- this.isWeb = true;
199
- this.root = webRoot;
200
- } else {
201
- this.isWeb = false;
202
- this.root = mobileRoot;
203
- }
204
-
205
- this.platform = null;
206
- if (config.capabilities.platformName) {
207
- this.platform = config.capabilities.platformName.toLowerCase();
208
- }
209
-
210
- return config;
211
- }
212
-
213
- static _config() {
214
- return [{
215
- name: 'app',
216
- message: 'Application package. Path to file or url',
217
- default: 'http://localhost',
218
- }, {
219
- name: 'platform',
220
- message: 'Mobile Platform',
221
- type: 'list',
222
- choices: ['iOS', supportedPlatform.android],
223
- default: supportedPlatform.android,
224
- }, {
225
- name: 'device',
226
- message: 'Device to run tests on',
227
- default: 'emulator',
228
- }];
229
- }
230
-
231
- async _startBrowser() {
232
- try {
233
- if (this.options.multiremote) {
234
- this.browser = await webdriverio.multiremote(this.options.multiremote);
235
- } else {
236
- this.browser = await webdriverio.remote(this.options);
237
- }
238
- } catch (err) {
239
- if (err.toString().indexOf('ECONNREFUSED')) {
240
- throw new ConnectionRefused(err);
241
- }
242
- throw err;
243
- }
244
- this.$$ = this.browser.$$.bind(this.browser);
245
-
246
- this.isRunning = true;
247
- if (this.options.timeouts && this.isWeb) {
248
- await this.defineTimeout(this.options.timeouts);
249
- }
250
- if (this.options.windowSize === 'maximize' && !this.platform) {
251
- const res = await this.browser.execute('return [screen.width, screen.height]');
252
- return this.browser.windowHandleSize({
253
- width: res.value[0],
254
- height: res.value[1],
255
- });
256
- }
257
- if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && !this.platform) {
258
- const dimensions = this.options.windowSize.split('x');
259
- await this.browser.windowHandleSize({
260
- width: dimensions[0],
261
- height: dimensions[1],
262
- });
263
- }
264
- }
265
-
266
- async _after() {
267
- if (!this.isRunning) return;
268
- if (this.options.restart) {
269
- this.isRunning = false;
270
- return this.browser.deleteSession();
271
- }
272
- if (this.isWeb && !this.platform) {
273
- return super._after();
274
- }
275
- }
276
-
277
- async _withinBegin(context) {
278
- if (this.isWeb) {
279
- return super._withinBegin(context);
280
- }
281
- if (context === 'webview') {
282
- return this.switchToWeb();
283
- }
284
- if (typeof context === 'object') {
285
- if (context.web) return this.switchToWeb(context.web);
286
- if (context.webview) return this.switchToWeb(context.webview);
287
- }
288
- return this._switchToContext(context);
289
- }
290
-
291
- _withinEnd() {
292
- if (this.isWeb) {
293
- return super._withinEnd();
294
- }
295
- return this.switchToNative();
296
- }
297
-
298
- _buildAppiumEndpoint() {
299
- const {
300
- protocol, port, hostname, path,
301
- } = this.browser.options;
302
- // Build path to Appium REST API endpoint
303
- return `${protocol}://${hostname}:${port}${path}`;
304
- }
305
-
306
- /**
307
- * Execute code only on iOS
308
- *
309
- * ```js
310
- * I.runOnIOS(() => {
311
- * I.click('//UIAApplication[1]/UIAWindow[1]/UIAButton[1]');
312
- * I.see('Hi, IOS', '~welcome');
313
- * });
314
- * ```
315
- *
316
- * Additional filter can be applied by checking for capabilities.
317
- * For instance, this code will be executed only on iPhone 5s:
318
- *
319
- *
320
- * ```js
321
- * I.runOnIOS({deviceName: 'iPhone 5s'},() => {
322
- * // ...
323
- * });
324
- * ```
325
- *
326
- * Also capabilities can be checked by a function.
327
- *
328
- * ```js
329
- * I.runOnAndroid((caps) => {
330
- * // caps is current config of desiredCapabiliites
331
- * return caps.platformVersion >= 6
332
- * },() => {
333
- * // ...
334
- * });
335
- * ```
336
- *
337
- * @param {*} caps
338
- * @param {*} fn
339
- */
340
- async runOnIOS(caps, fn) {
341
- if (this.platform !== 'ios') return;
342
- recorder.session.start('iOS-only actions');
343
- this._runWithCaps(caps, fn);
344
- recorder.add('restore from iOS session', () => recorder.session.restore());
345
- return recorder.promise();
346
- }
347
-
348
- /**
349
- * Execute code only on Android
350
- *
351
- * ```js
352
- * I.runOnAndroid(() => {
353
- * I.click('io.selendroid.testapp:id/buttonTest');
354
- * });
355
- * ```
356
- *
357
- * Additional filter can be applied by checking for capabilities.
358
- * For instance, this code will be executed only on Android 6.0:
359
- *
360
- *
361
- * ```js
362
- * I.runOnAndroid({platformVersion: '6.0'},() => {
363
- * // ...
364
- * });
365
- * ```
366
- *
367
- * Also capabilities can be checked by a function.
368
- * In this case, code will be executed only on Android >= 6.
369
- *
370
- * ```js
371
- * I.runOnAndroid((caps) => {
372
- * // caps is current config of desiredCapabiliites
373
- * return caps.platformVersion >= 6
374
- * },() => {
375
- * // ...
376
- * });
377
- * ```
378
- *
379
- * @param {*} caps
380
- * @param {*} fn
381
- */
382
- async runOnAndroid(caps, fn) {
383
- if (this.platform !== 'android') return;
384
- recorder.session.start('Android-only actions');
385
- this._runWithCaps(caps, fn);
386
- recorder.add('restore from Android session', () => recorder.session.restore());
387
- return recorder.promise();
388
- }
389
-
390
- /**
391
- * Execute code only in Web mode.
392
- *
393
- * ```js
394
- * I.runInWeb(() => {
395
- * I.waitForElement('#data');
396
- * I.seeInCurrentUrl('/data');
397
- * });
398
- * ```
399
- *
400
- * @param {*} fn
401
- */
402
- /* eslint-disable */
403
- async runInWeb(fn) {
404
- if (!this.isWeb) return;
405
- recorder.session.start('Web-only actions');
406
-
407
- recorder.add('restore from Web session', () => recorder.session.restore(), true);
408
- return recorder.promise();
409
- }
410
- /* eslint-enable */
411
-
412
- async _runWithCaps(caps, fn) {
413
- if (typeof caps === 'object') {
414
- for (const key in caps) {
415
- // skip if capabilities do not match
416
- if (this.config.desiredCapabilities[key] !== caps[key]) {
417
- return;
418
- }
419
- }
420
- }
421
- if (typeof caps === 'function') {
422
- if (!fn) {
423
- fn = caps;
424
- } else {
425
- // skip if capabilities are checked inside a function
426
- const enabled = caps(this.config.desiredCapabilities);
427
- if (!enabled) return;
428
- }
429
- }
430
-
431
- fn();
432
- }
433
-
434
- /**
435
- * Returns app installation status.
436
- *
437
- * ```js
438
- * I.checkIfAppIsInstalled("com.example.android.apis");
439
- * ```
440
- *
441
- * @param {string} bundleId String ID of bundled app
442
- * @return {Promise<boolean>}
443
- *
444
- * Appium: support only Android
445
- */
446
- async checkIfAppIsInstalled(bundleId) {
447
- onlyForApps.call(this, supportedPlatform.android);
448
- return this.browser.isAppInstalled(bundleId);
449
- }
450
-
451
- /**
452
- * Check if an app is installed.
453
- *
454
- * ```js
455
- * I.seeAppIsInstalled("com.example.android.apis");
456
- * ```
457
- *
458
- * @param {string} bundleId String ID of bundled app
459
- * @return {Promise<void>}
460
- *
461
- * Appium: support only Android
462
- */
463
- async seeAppIsInstalled(bundleId) {
464
- onlyForApps.call(this, supportedPlatform.android);
465
- const res = await this.browser.isAppInstalled(bundleId);
466
- return truth(`app ${bundleId}`, 'to be installed').assert(res);
467
- }
468
-
469
- /**
470
- * Check if an app is not installed.
471
- *
472
- * ```js
473
- * I.seeAppIsNotInstalled("com.example.android.apis");
474
- * ```
475
- *
476
- * @param {string} bundleId String ID of bundled app
477
- * @return {Promise<void>}
478
- *
479
- * Appium: support only Android
480
- */
481
- async seeAppIsNotInstalled(bundleId) {
482
- onlyForApps.call(this, supportedPlatform.android);
483
- const res = await this.browser.isAppInstalled(bundleId);
484
- return truth(`app ${bundleId}`, 'to be installed').negate(res);
485
- }
486
-
487
- /**
488
- * Install an app on device.
489
- *
490
- * ```js
491
- * I.installApp('/path/to/file.apk');
492
- * ```
493
- * @param {string} path path to apk file
494
- * @return {Promise<void>}
495
- *
496
- * Appium: support only Android
497
- */
498
- async installApp(path) {
499
- onlyForApps.call(this, supportedPlatform.android);
500
- return this.browser.installApp(path);
501
- }
502
-
503
- /**
504
- * Remove an app from the device.
505
- *
506
- * ```js
507
- * I.removeApp('appName', 'com.example.android.apis');
508
- * ```
509
- *
510
- * Appium: support only Android
511
- *
512
- * @param {string} appId
513
- * @param {string} [bundleId] ID of bundle
514
- */
515
- async removeApp(appId, bundleId) {
516
- onlyForApps.call(this, supportedPlatform.android);
517
-
518
- return this.axios({
519
- method: 'post',
520
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/device/remove_app`,
521
- data: { appId, bundleId },
522
- });
523
- }
524
-
525
- /**
526
- * Reset the currently running app for current session.
527
- *
528
- * ```js
529
- * I.resetApp();
530
- * ```
531
- *
532
- */
533
- async resetApp() {
534
- onlyForApps.call(this);
535
- return this.axios({
536
- method: 'post',
537
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/app/reset`,
538
- });
539
- }
540
-
541
- /**
542
- * Check current activity on an Android device.
543
- *
544
- * ```js
545
- * I.seeCurrentActivityIs(".HomeScreenActivity")
546
- * ```
547
- * @param {string} currentActivity
548
- * @return {Promise<void>}
549
- *
550
- * Appium: support only Android
551
- */
552
- async seeCurrentActivityIs(currentActivity) {
553
- onlyForApps.call(this, supportedPlatform.android);
554
- const res = await this.browser.getCurrentActivity();
555
- return truth('current activity', `to be ${currentActivity}`).assert(res === currentActivity);
556
- }
557
-
558
- /**
559
- * Check whether the device is locked.
560
- *
561
- * ```js
562
- * I.seeDeviceIsLocked();
563
- * ```
564
- *
565
- * @return {Promise<void>}
566
- *
567
- * Appium: support only Android
568
- */
569
- async seeDeviceIsLocked() {
570
- onlyForApps.call(this, supportedPlatform.android);
571
- const res = await this.browser.isLocked();
572
- return truth('device', 'to be locked').assert(res);
573
- }
574
-
575
- /**
576
- * Check whether the device is not locked.
577
- *
578
- * ```js
579
- * I.seeDeviceIsUnlocked();
580
- * ```
581
- *
582
- * @return {Promise<void>}
583
- *
584
- * Appium: support only Android
585
- */
586
- async seeDeviceIsUnlocked() {
587
- onlyForApps.call(this, supportedPlatform.android);
588
- const res = await this.browser.isLocked();
589
- return truth('device', 'to be locked').negate(res);
590
- }
591
-
592
- /**
593
- * Check the device orientation
594
- *
595
- * ```js
596
- * I.seeOrientationIs('PORTRAIT');
597
- * I.seeOrientationIs('LANDSCAPE')
598
- * ```
599
- *
600
- * @return {Promise<void>}
601
- *
602
- * @param {'LANDSCAPE'|'PORTRAIT'} orientation LANDSCAPE or PORTRAIT
603
- *
604
- * Appium: support Android and iOS
605
- */
606
- async seeOrientationIs(orientation) {
607
- onlyForApps.call(this);
608
-
609
- const res = await this.axios({
610
- method: 'get',
611
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
612
- });
613
-
614
- const currentOrientation = res.data.value;
615
- return truth('orientation', `to be ${orientation}`).assert(currentOrientation === orientation);
616
- }
617
-
618
- /**
619
- * Set a device orientation. Will fail, if app will not set orientation
620
- *
621
- * ```js
622
- * I.setOrientation('PORTRAIT');
623
- * I.setOrientation('LANDSCAPE')
624
- * ```
625
- *
626
- * @param {'LANDSCAPE'|'PORTRAIT'} orientation LANDSCAPE or PORTRAIT
627
- *
628
- * Appium: support Android and iOS
629
- */
630
- async setOrientation(orientation) {
631
- onlyForApps.call(this);
632
-
633
- return this.axios({
634
- method: 'post',
635
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
636
- data: { orientation },
637
- });
638
- }
639
-
640
- /**
641
- * Get list of all available contexts
642
- *
643
- * ```
644
- * let contexts = await I.grabAllContexts();
645
- * ```
646
- *
647
- * @return {Promise<string[]>}
648
- *
649
- * Appium: support Android and iOS
650
- */
651
- async grabAllContexts() {
652
- onlyForApps.call(this);
653
- return this.browser.getContexts();
654
- }
655
-
656
- /**
657
- * Retrieve current context
658
- *
659
- * ```js
660
- * let context = await I.grabContext();
661
- * ```
662
- *
663
- * @return {Promise<string|null>}
664
- *
665
- * Appium: support Android and iOS
666
- */
667
- async grabContext() {
668
- onlyForApps.call(this);
669
- return this.browser.getContext();
670
- }
671
-
672
- /**
673
- * Get current device activity.
674
- *
675
- * ```js
676
- * let activity = await I.grabCurrentActivity();
677
- * ```
678
- *
679
- * @return {Promise<string>}
680
- *
681
- * Appium: support only Android
682
- */
683
- async grabCurrentActivity() {
684
- onlyForApps.call(this, supportedPlatform.android);
685
- return this.browser.getCurrentActivity();
686
- }
687
-
688
- /**
689
- * Get information about the current network connection (Data/WIFI/Airplane).
690
- * The actual server value will be a number. However WebdriverIO additional
691
- * properties to the response object to allow easier assertions.
692
- *
693
- * ```js
694
- * let con = await I.grabNetworkConnection();
695
- * ```
696
- *
697
- * @return {Promise<{}>}
698
- *
699
- * Appium: support only Android
700
- */
701
- async grabNetworkConnection() {
702
- onlyForApps.call(this, supportedPlatform.android);
703
- const res = await this.browser.getNetworkConnection();
704
- return {
705
- value: res,
706
- inAirplaneMode: res.inAirplaneMode,
707
- hasWifi: res.hasWifi,
708
- hasData: res.hasData,
709
- };
710
- }
711
-
712
- /**
713
- * Get current orientation.
714
- *
715
- * ```js
716
- * let orientation = await I.grabOrientation();
717
- * ```
718
- *
719
- * @return {Promise<string>}
720
- *
721
- * Appium: support Android and iOS
722
- */
723
- async grabOrientation() {
724
- onlyForApps.call(this);
725
- const res = await this.browser.orientation();
726
- this.debugSection('Orientation', res);
727
- return res;
728
- }
729
-
730
- /**
731
- * Get all the currently specified settings.
732
- *
733
- * ```js
734
- * let settings = await I.grabSettings();
735
- * ```
736
- *
737
- * @return {Promise<string>}
738
- *
739
- * Appium: support Android and iOS
740
- */
741
- async grabSettings() {
742
- onlyForApps.call(this);
743
- const res = await this.browser.getSettings();
744
- this.debugSection('Settings', JSON.stringify(res));
745
- return res;
746
- }
747
-
748
- /**
749
- * Switch to the specified context.
750
- *
751
- * @param {*} context the context to switch to
752
- */
753
- async _switchToContext(context) {
754
- return this.browser.switchContext(context);
755
- }
756
-
757
- /**
758
- * Switches to web context.
759
- * If no context is provided switches to the first detected web context
760
- *
761
- * ```js
762
- * // switch to first web context
763
- * I.switchToWeb();
764
- *
765
- * // or set the context explicitly
766
- * I.switchToWeb('WEBVIEW_io.selendroid.testapp');
767
- * ```
768
- *
769
- * @return {Promise<void>}
770
- *
771
- * @param {string} [context]
772
- */
773
- async switchToWeb(context) {
774
- this.isWeb = true;
775
- this.defaultContext = 'body';
776
-
777
- if (context) return this._switchToContext(context);
778
- const contexts = await this.grabAllContexts();
779
- this.debugSection('Contexts', contexts.toString());
780
- for (const idx in contexts) {
781
- if (contexts[idx].match(/^WEBVIEW/)) return this._switchToContext(contexts[idx]);
782
- }
783
-
784
- throw new Error('No WEBVIEW could be guessed, please specify one in params');
785
- }
786
-
787
- /**
788
- * Switches to native context.
789
- * By default switches to NATIVE_APP context unless other specified.
790
- *
791
- * ```js
792
- * I.switchToNative();
793
- *
794
- * // or set context explicitly
795
- * I.switchToNative('SOME_OTHER_CONTEXT');
796
- * ```
797
- * @param {*} [context]
798
- * @return {Promise<void>}
799
- */
800
- async switchToNative(context = null) {
801
- this.isWeb = false;
802
- this.defaultContext = '//*';
803
-
804
- if (context) return this._switchToContext(context);
805
- return this._switchToContext('NATIVE_APP');
806
- }
807
-
808
- /**
809
- * Start an arbitrary Android activity during a session.
810
- *
811
- * ```js
812
- * I.startActivity('io.selendroid.testapp', '.RegisterUserActivity');
813
- * ```
814
- *
815
- * Appium: support only Android
816
- *
817
- * @param {string} appPackage
818
- * @param {string} appActivity
819
- * @return {Promise<void>}
820
- */
821
- async startActivity(appPackage, appActivity) {
822
- onlyForApps.call(this, supportedPlatform.android);
823
- return this.browser.startActivity(appPackage, appActivity);
824
- }
825
-
826
- /**
827
- * Set network connection mode.
828
- *
829
- * * airplane mode
830
- * * wifi mode
831
- * * data data
832
- *
833
- * ```js
834
- * I.setNetworkConnection(0) // airplane mode off, wifi off, data off
835
- * I.setNetworkConnection(1) // airplane mode on, wifi off, data off
836
- * I.setNetworkConnection(2) // airplane mode off, wifi on, data off
837
- * I.setNetworkConnection(4) // airplane mode off, wifi off, data on
838
- * I.setNetworkConnection(6) // airplane mode off, wifi on, data on
839
- * ```
840
- * See corresponding [webdriverio reference](http://webdriver.io/api/mobile/setNetworkConnection.html).
841
- *
842
- * @return {Promise<{}>}
843
- *
844
- * Appium: support only Android
845
- */
846
- async setNetworkConnection(value) {
847
- onlyForApps.call(this, supportedPlatform.android);
848
- return this.browser.setNetworkConnection(value);
849
- }
850
-
851
- /**
852
- * Update the current setting on the device
853
- *
854
- * ```js
855
- * I.setSettings({cyberdelia: 'open'});
856
- * ```
857
- *
858
- * @param {object} settings object
859
- *
860
- * Appium: support Android and iOS
861
- */
862
- async setSettings(settings) {
863
- onlyForApps.call(this);
864
- return this.browser.settings(settings);
865
- }
866
-
867
- /**
868
- * Hide the keyboard.
869
- *
870
- * ```js
871
- * // taps outside to hide keyboard per default
872
- * I.hideDeviceKeyboard();
873
- * I.hideDeviceKeyboard('tapOutside');
874
- *
875
- * // or by pressing key
876
- * I.hideDeviceKeyboard('pressKey', 'Done');
877
- * ```
878
- *
879
- * Appium: support Android and iOS
880
- *
881
- * @param {'tapOutside' | 'pressKey'} [strategy] Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
882
- * @param {string} [key] Optional key
883
- */
884
- async hideDeviceKeyboard(strategy, key) {
885
- onlyForApps.call(this);
886
- strategy = strategy || 'tapOutside';
887
- return this.browser.hideKeyboard(strategy, key);
888
- }
889
-
890
- /**
891
- * Send a key event to the device.
892
- * List of keys: https://developer.android.com/reference/android/view/KeyEvent.html
893
- *
894
- * ```js
895
- * I.sendDeviceKeyEvent(3);
896
- * ```
897
- *
898
- * @param {number} keyValue Device specific key value
899
- * @return {Promise<void>}
900
- *
901
- * Appium: support only Android
902
- */
903
- async sendDeviceKeyEvent(keyValue) {
904
- onlyForApps.call(this, supportedPlatform.android);
905
- return this.browser.pressKeyCode(keyValue);
906
- }
907
-
908
- /**
909
- * Open the notifications panel on the device.
910
- *
911
- * ```js
912
- * I.openNotifications();
913
- * ```
914
- *
915
- * @return {Promise<void>}
916
- *
917
- * Appium: support only Android
918
- */
919
- async openNotifications() {
920
- onlyForApps.call(this, supportedPlatform.android);
921
- return this.browser.openNotifications();
922
- }
923
-
924
- /**
925
- * The Touch Action API provides the basis of all gestures that can be
926
- * automated in Appium. At its core is the ability to chain together ad hoc
927
- * individual actions, which will then be applied to an element in the
928
- * application on the device.
929
- * [See complete documentation](http://webdriver.io/api/mobile/touchAction.html)
930
- *
931
- * ```js
932
- * I.makeTouchAction("~buttonStartWebviewCD", 'tap');
933
- * ```
934
- *
935
- * @return {Promise<void>}
936
- *
937
- * Appium: support Android and iOS
938
- */
939
- async makeTouchAction(locator, action) {
940
- onlyForApps.call(this);
941
- const element = await this.browser.$(parseLocator.call(this, locator));
942
-
943
- return this.browser.touchAction({
944
- action,
945
- element,
946
- });
947
- }
948
-
949
- /**
950
- * Taps on element.
951
- *
952
- * ```js
953
- * I.tap("~buttonStartWebviewCD");
954
- * ```
955
- *
956
- * Shortcut for `makeTouchAction`
957
- *
958
- * @return {Promise<void>}
959
- *
960
- * @param {*} locator
961
- */
962
- async tap(locator) {
963
- return this.makeTouchAction(locator, 'tap');
964
- }
965
-
966
- /**
967
- * Perform a swipe on the screen or an element.
968
- *
969
- * ```js
970
- * let locator = "#io.selendroid.testapp:id/LinearLayout1";
971
- * I.swipe(locator, 800, 1200, 1000);
972
- * ```
973
- *
974
- * [See complete reference](http://webdriver.io/api/mobile/swipe.html)
975
- *
976
- * @param {CodeceptJS.LocatorOrString} locator
977
- * @param {number} xoffset
978
- * @param {number} yoffset
979
- * @param {number} [speed=1000] (optional), 1000 by default
980
- * @return {Promise<void>}
981
- *
982
- * Appium: support Android and iOS
983
- */
984
- /* eslint-disable */
985
- async swipe(locator, xoffset, yoffset, speed = 1000) {
986
- onlyForApps.call(this);
987
- const res = await this.browser.$(parseLocator.call(this, locator));
988
- // if (!res.length) throw new ElementNotFound(locator, 'was not found in UI');
989
- return this.performSwipe(await res.getLocation(), { x: (await res.getLocation()).x + xoffset, y: (await res.getLocation()).y + yoffset });
990
- }
991
- /* eslint-enable */
992
-
993
- /**
994
- * Perform a swipe on the screen.
995
- *
996
- * ```js
997
- * I.performSwipe({ x: 300, y: 100 }, { x: 200, y: 100 });
998
- * ```
999
- *
1000
- * @param {object} from
1001
- * @param {object} to
1002
- *
1003
- * Appium: support Android and iOS
1004
- */
1005
- async performSwipe(from, to) {
1006
- await this.browser.touchPerform([{
1007
- action: 'press',
1008
- options: from,
1009
- }, {
1010
- action: 'wait',
1011
- options: { ms: 1000 },
1012
- }, {
1013
- action: 'moveTo',
1014
- options: to,
1015
- }, {
1016
- action: 'release',
1017
- }]);
1018
- await this.browser.pause(1000);
1019
- }
1020
-
1021
- /**
1022
- * Perform a swipe down on an element.
1023
- *
1024
- * ```js
1025
- * let locator = "#io.selendroid.testapp:id/LinearLayout1";
1026
- * I.swipeDown(locator); // simple swipe
1027
- * I.swipeDown(locator, 500); // set speed
1028
- * I.swipeDown(locator, 1200, 1000); // set offset and speed
1029
- * ```
1030
- *
1031
- * @param {CodeceptJS.LocatorOrString} locator
1032
- * @param {number} [yoffset] (optional)
1033
- * @param {number} [speed=1000] (optional), 1000 by default
1034
- * @return {Promise<void>}
1035
- *
1036
- * Appium: support Android and iOS
1037
- */
1038
- async swipeDown(locator, yoffset = 1000, speed) {
1039
- onlyForApps.call(this);
1040
-
1041
- if (!speed) {
1042
- speed = yoffset;
1043
- yoffset = 100;
1044
- }
1045
-
1046
- return this.swipe(locator, 0, yoffset, speed);
1047
- }
1048
-
1049
- /**
1050
- *
1051
- * Perform a swipe left on an element.
1052
- *
1053
- * ```js
1054
- * let locator = "#io.selendroid.testapp:id/LinearLayout1";
1055
- * I.swipeLeft(locator); // simple swipe
1056
- * I.swipeLeft(locator, 500); // set speed
1057
- * I.swipeLeft(locator, 1200, 1000); // set offset and speed
1058
- * ```
1059
- *
1060
- * @param {CodeceptJS.LocatorOrString} locator
1061
- * @param {number} [xoffset] (optional)
1062
- * @param {number} [speed=1000] (optional), 1000 by default
1063
- * @return {Promise<void>}
1064
- *
1065
- * Appium: support Android and iOS
1066
- */
1067
- async swipeLeft(locator, xoffset = 1000, speed) {
1068
- onlyForApps.call(this);
1069
- if (!speed) {
1070
- speed = xoffset;
1071
- xoffset = 100;
1072
- }
1073
-
1074
- return this.swipe(parseLocator.call(this, locator), -xoffset, 0, speed);
1075
- }
1076
-
1077
- /**
1078
- * Perform a swipe right on an element.
1079
- *
1080
- * ```js
1081
- * let locator = "#io.selendroid.testapp:id/LinearLayout1";
1082
- * I.swipeRight(locator); // simple swipe
1083
- * I.swipeRight(locator, 500); // set speed
1084
- * I.swipeRight(locator, 1200, 1000); // set offset and speed
1085
- * ```
1086
- *
1087
- * @param {CodeceptJS.LocatorOrString} locator
1088
- * @param {number} [xoffset] (optional)
1089
- * @param {number} [speed=1000] (optional), 1000 by default
1090
- * @return {Promise<void>}
1091
- *
1092
- * Appium: support Android and iOS
1093
- */
1094
- async swipeRight(locator, xoffset = 1000, speed) {
1095
- onlyForApps.call(this);
1096
- if (!speed) {
1097
- speed = xoffset;
1098
- xoffset = 100;
1099
- }
1100
-
1101
- return this.swipe(parseLocator.call(this, locator), xoffset, 0, speed);
1102
- }
1103
-
1104
- /**
1105
- * Perform a swipe up on an element.
1106
- *
1107
- * ```js
1108
- * let locator = "#io.selendroid.testapp:id/LinearLayout1";
1109
- * I.swipeUp(locator); // simple swipe
1110
- * I.swipeUp(locator, 500); // set speed
1111
- * I.swipeUp(locator, 1200, 1000); // set offset and speed
1112
- * ```
1113
- *
1114
- * @param {CodeceptJS.LocatorOrString} locator
1115
- * @param {number} [yoffset] (optional)
1116
- * @param {number} [speed=1000] (optional), 1000 by default
1117
- * @return {Promise<void>}
1118
- *
1119
- * Appium: support Android and iOS
1120
- */
1121
- async swipeUp(locator, yoffset = 1000, speed) {
1122
- onlyForApps.call(this);
1123
-
1124
- if (!speed) {
1125
- speed = yoffset;
1126
- yoffset = 100;
1127
- }
1128
-
1129
- return this.swipe(parseLocator.call(this, locator), 0, -yoffset, speed);
1130
- }
1131
-
1132
- /**
1133
- * Perform a swipe in selected direction on an element to searchable element.
1134
- *
1135
- * ```js
1136
- * I.swipeTo(
1137
- * "android.widget.CheckBox", // searchable element
1138
- * "//android.widget.ScrollView/android.widget.LinearLayout", // scroll element
1139
- * "up", // direction
1140
- * 30,
1141
- * 100,
1142
- * 500);
1143
- * ```
1144
- *
1145
- * @param {string} searchableLocator
1146
- * @param {string} scrollLocator
1147
- * @param {string} direction
1148
- * @param {number} timeout
1149
- * @param {number} offset
1150
- * @param {number} speed
1151
- * @return {Promise<void>}
1152
- *
1153
- * Appium: support Android and iOS
1154
- */
1155
- async swipeTo(searchableLocator, scrollLocator, direction, timeout, offset, speed) {
1156
- onlyForApps.call(this);
1157
- direction = direction || 'down';
1158
- switch (direction) {
1159
- case 'down':
1160
- direction = 'swipeDown';
1161
- break;
1162
- case 'up':
1163
- direction = 'swipeUp';
1164
- break;
1165
- case 'left':
1166
- direction = 'swipeLeft';
1167
- break;
1168
- case 'right':
1169
- direction = 'swipeRight';
1170
- break;
1171
- }
1172
- timeout = timeout || this.options.waitForTimeoutInSeconds;
1173
-
1174
- const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds`;
1175
- const browser = this.browser;
1176
- let err = false;
1177
- let currentSource;
1178
- return browser.waitUntil(() => {
1179
- if (err) {
1180
- return new Error(`Scroll to the end and element ${searchableLocator} was not found`);
1181
- }
1182
- return browser.$$(parseLocator.call(this, searchableLocator))
1183
- .then(els => els.length && els[0].isDisplayed())
1184
- .then((res) => {
1185
- if (res) {
1186
- return true;
1187
- }
1188
- return this[direction](scrollLocator, offset, speed).getSource().then((source) => {
1189
- if (source === currentSource) {
1190
- err = true;
1191
- } else {
1192
- currentSource = source;
1193
- return false;
1194
- }
1195
- });
1196
- });
1197
- }, timeout * 1000, errorMsg)
1198
- .catch((e) => {
1199
- if (e.message.indexOf('timeout') && e.type !== 'NoSuchElement') {
1200
- throw new AssertionFailedError({ customMessage: `Scroll to the end and element ${searchableLocator} was not found` }, '');
1201
- } else {
1202
- throw e;
1203
- }
1204
- });
1205
- }
1206
-
1207
- /**
1208
- * Performs a specific touch action.
1209
- * The action object need to contain the action name, x/y coordinates
1210
- *
1211
- * ```js
1212
- * I.touchPerform([{
1213
- * action: 'press',
1214
- * options: {
1215
- * x: 100,
1216
- * y: 200
1217
- * }
1218
- * }, {action: 'release'}])
1219
- *
1220
- * I.touchPerform([{
1221
- * action: 'tap',
1222
- * options: {
1223
- * element: '1', // json web element was queried before
1224
- * x: 10, // x offset
1225
- * y: 20, // y offset
1226
- * count: 1 // number of touches
1227
- * }
1228
- * }]);
1229
- * ```
1230
- *
1231
- * Appium: support Android and iOS
1232
- *
1233
- * @param {Array} actions Array of touch actions
1234
- */
1235
- async touchPerform(actions) {
1236
- onlyForApps.call(this);
1237
- return this.browser.touchPerform(actions);
1238
- }
1239
-
1240
- /**
1241
- * Pulls a file from the device.
1242
- *
1243
- * ```js
1244
- * I.pullFile('/storage/emulated/0/DCIM/logo.png', 'my/path');
1245
- * // save file to output dir
1246
- * I.pullFile('/storage/emulated/0/DCIM/logo.png', output_dir);
1247
- * ```
1248
- *
1249
- * @param {string} path
1250
- * @param {string} dest
1251
- * @return {Promise<string>}
1252
- *
1253
- * Appium: support Android and iOS
1254
- */
1255
- async pullFile(path, dest) {
1256
- onlyForApps.call(this);
1257
- return this.browser.pullFile(path).then(res => fs.writeFile(dest, Buffer.from(res, 'base64'), (err) => {
1258
- if (err) {
1259
- return false;
1260
- }
1261
- return true;
1262
- }));
1263
- }
1264
-
1265
- /**
1266
- * Perform a shake action on the device.
1267
- *
1268
- * ```js
1269
- * I.shakeDevice();
1270
- * ```
1271
- *
1272
- * @return {Promise<void>}
1273
- *
1274
- * Appium: support only iOS
1275
- */
1276
- async shakeDevice() {
1277
- onlyForApps.call(this, 'iOS');
1278
- return this.browser.shake();
1279
- }
1280
-
1281
- /**
1282
- * Perform a rotation gesture centered on the specified element.
1283
- *
1284
- * ```js
1285
- * I.rotate(120, 120)
1286
- * ```
1287
- *
1288
- * See corresponding [webdriverio reference](http://webdriver.io/api/mobile/rotate.html).
1289
- *
1290
- * @return {Promise<void>}
1291
- *
1292
- * Appium: support only iOS
1293
- */
1294
- async rotate(x, y, duration, radius, rotation, touchCount) {
1295
- onlyForApps.call(this, 'iOS');
1296
- return this.browser.rotate(x, y, duration, radius, rotation, touchCount);
1297
- }
1298
-
1299
- /**
1300
- * Set immediate value in app.
1301
- *
1302
- * See corresponding [webdriverio reference](http://webdriver.io/api/mobile/setImmediateValue.html).
1303
- *
1304
- * @return {Promise<void>}
1305
- *
1306
- * Appium: support only iOS
1307
- */
1308
- async setImmediateValue(id, value) {
1309
- onlyForApps.call(this, 'iOS');
1310
- return this.browser.setImmediateValue(id, value);
1311
- }
1312
-
1313
- /**
1314
- * Simulate Touch ID with either valid (match == true) or invalid (match == false) fingerprint.
1315
- *
1316
- * ```js
1317
- * I.touchId(); // simulates valid fingerprint
1318
- * I.touchId(true); // simulates valid fingerprint
1319
- * I.touchId(false); // simulates invalid fingerprint
1320
- * ```
1321
- *
1322
- * @return {Promise<void>}
1323
- *
1324
- * Appium: support only iOS
1325
- * TODO: not tested
1326
- */
1327
- async simulateTouchId(match) {
1328
- onlyForApps.call(this, 'iOS');
1329
- match = match || true;
1330
- return this.browser.touchId(match);
1331
- }
1332
-
1333
- /**
1334
- * Close the given application.
1335
- *
1336
- * ```js
1337
- * I.closeApp();
1338
- * ```
1339
- *
1340
- * @return {Promise<void>}
1341
- *
1342
- * Appium: support only iOS
1343
- */
1344
- async closeApp() {
1345
- onlyForApps.call(this, 'iOS');
1346
- return this.browser.closeApp();
1347
- }
1348
-
1349
- /**
1350
- * Appends text to a input field or textarea.
1351
- * Field is located by name, label, CSS or XPath
1352
- *
1353
- * ```js
1354
- * I.appendField('#myTextField', 'appended');
1355
- * ```
1356
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
1357
- * @param {string} value text value to append.
1358
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1359
- *
1360
- *
1361
- */
1362
- async appendField(field, value) {
1363
- if (this.isWeb) return super.appendField(field, value);
1364
- return super.appendField(parseLocator.call(this, field), value);
1365
- }
1366
-
1367
- /**
1368
- * Selects a checkbox or radio button.
1369
- * Element is located by label or name or CSS or XPath.
1370
- *
1371
- * The second parameter is a context (CSS or XPath locator) to narrow the search.
1372
- *
1373
- * ```js
1374
- * I.checkOption('#agree');
1375
- * I.checkOption('I Agree to Terms and Conditions');
1376
- * I.checkOption('agree', '//form');
1377
- * ```
1378
- * @param {CodeceptJS.LocatorOrString} field checkbox located by label | name | CSS | XPath | strict locator.
1379
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
1380
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1381
- *
1382
- *
1383
- */
1384
- async checkOption(field) {
1385
- if (this.isWeb) return super.checkOption(field);
1386
- return super.checkOption(parseLocator.call(this, field));
1387
- }
1388
-
1389
- /**
1390
- * Perform a click on a link or a button, given by a locator.
1391
- * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
1392
- * For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
1393
- * For images, the "alt" attribute and inner text of any parent links are searched.
1394
- *
1395
- * The second parameter is a context (CSS or XPath locator) to narrow the search.
1396
- *
1397
- * ```js
1398
- * // simple link
1399
- * I.click('Logout');
1400
- * // button of form
1401
- * I.click('Submit');
1402
- * // CSS button
1403
- * I.click('#form input[type=submit]');
1404
- * // XPath
1405
- * I.click('//form/*[@type=submit]');
1406
- * // link in context
1407
- * I.click('Logout', '#nav');
1408
- * // using strict locator
1409
- * I.click({css: 'nav a.login'});
1410
- * ```
1411
- *
1412
- * @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
1413
- * @param {?CodeceptJS.LocatorOrString | null} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
1414
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1415
- *
1416
- *
1417
- */
1418
- async click(locator, context) {
1419
- if (this.isWeb) return super.click(locator, context);
1420
- return super.click(parseLocator.call(this, locator), parseLocator.call(this, context));
1421
- }
1422
-
1423
- /**
1424
- * Verifies that the specified checkbox is not checked.
1425
- *
1426
- * ```js
1427
- * I.dontSeeCheckboxIsChecked('#agree'); // located by ID
1428
- * I.dontSeeCheckboxIsChecked('I agree to terms'); // located by label
1429
- * I.dontSeeCheckboxIsChecked('agree'); // located by name
1430
- * ```
1431
- *
1432
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1433
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1434
- *
1435
- *
1436
- */
1437
- async dontSeeCheckboxIsChecked(field) {
1438
- if (this.isWeb) return super.dontSeeCheckboxIsChecked(field);
1439
- return super.dontSeeCheckboxIsChecked(parseLocator.call(this, field));
1440
- }
1441
-
1442
- /**
1443
- * Opposite to `seeElement`. Checks that element is not visible (or in DOM)
1444
- *
1445
- * ```js
1446
- * I.dontSeeElement('.modal'); // modal is not shown
1447
- * ```
1448
- *
1449
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|Strict locator.
1450
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1451
- *
1452
- */
1453
- async dontSeeElement(locator) {
1454
- if (this.isWeb) return super.dontSeeElement(locator);
1455
- return super.dontSeeElement(parseLocator.call(this, locator));
1456
- }
1457
-
1458
- /**
1459
- * Checks that value of input field or textarea doesn't equal to given value
1460
- * Opposite to `seeInField`.
1461
- *
1462
- * ```js
1463
- * I.dontSeeInField('email', 'user@user.com'); // field by name
1464
- * I.dontSeeInField({ css: 'form input.email' }, 'user@user.com'); // field by CSS
1465
- * ```
1466
- *
1467
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1468
- * @param {string} value value to check.
1469
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1470
- *
1471
- *
1472
- */
1473
- async dontSeeInField(field, value) {
1474
- if (this.isWeb) return super.dontSeeInField(field, value);
1475
- return super.dontSeeInField(parseLocator.call(this, field), value);
1476
- }
1477
-
1478
- /**
1479
- * Opposite to `see`. Checks that a text is not present on a page.
1480
- * Use context parameter to narrow down the search.
1481
- *
1482
- * ```js
1483
- * I.dontSee('Login'); // assume we are already logged in.
1484
- * I.dontSee('Login', '.nav'); // no login inside .nav element
1485
- * ```
1486
- *
1487
- * @param {string} text which is not present.
1488
- * @param {CodeceptJS.LocatorOrString} [context] (optional) element located by CSS|XPath|strict locator in which to perfrom search.
1489
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1490
- *
1491
- */
1492
- async dontSee(text, context = null) {
1493
- if (this.isWeb) return super.dontSee(text, context);
1494
- return super.dontSee(text, parseLocator.call(this, context));
1495
- }
1496
-
1497
- /**
1498
- * Fills a text field or textarea, after clearing its value, with the given string.
1499
- * Field is located by name, label, CSS, or XPath.
1500
- *
1501
- * ```js
1502
- * // by label
1503
- * I.fillField('Email', 'hello@world.com');
1504
- * // by name
1505
- * I.fillField('password', secret('123456'));
1506
- * // by CSS
1507
- * I.fillField('form#login input[name=username]', 'John');
1508
- * // or by strict locator
1509
- * I.fillField({css: 'form#login input[name=username]'}, 'John');
1510
- * ```
1511
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1512
- * @param {CodeceptJS.StringOrSecret} value text value to fill.
1513
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1514
- *
1515
- *
1516
- */
1517
- async fillField(field, value) {
1518
- value = value.toString();
1519
- if (this.isWeb) return super.fillField(field, value);
1520
- return super.fillField(parseLocator.call(this, field), value);
1521
- }
1522
-
1523
- /**
1524
- * Retrieves all texts from an element located by CSS or XPath and returns it to test.
1525
- * Resumes test execution, so **should be used inside async with `await`** operator.
1526
- *
1527
- * ```js
1528
- * let pins = await I.grabTextFromAll('#pin li');
1529
- * ```
1530
- *
1531
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1532
- * @returns {Promise<string[]>} attribute value
1533
- *
1534
- *
1535
- */
1536
- async grabTextFromAll(locator) {
1537
- if (this.isWeb) return super.grabTextFromAll(locator);
1538
- return super.grabTextFromAll(parseLocator.call(this, locator));
1539
- }
1540
-
1541
- /**
1542
- * Retrieves a text from an element located by CSS or XPath and returns it to test.
1543
- * Resumes test execution, so **should be used inside async with `await`** operator.
1544
- *
1545
- * ```js
1546
- * let pin = await I.grabTextFrom('#pin');
1547
- * ```
1548
- * If multiple elements found returns first element.
1549
- *
1550
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1551
- * @returns {Promise<string>} attribute value
1552
- *
1553
- *
1554
- */
1555
- async grabTextFrom(locator) {
1556
- if (this.isWeb) return super.grabTextFrom(locator);
1557
- return super.grabTextFrom(parseLocator.call(this, locator));
1558
- }
1559
-
1560
- /**
1561
- * Grab number of visible elements by locator.
1562
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1563
- *
1564
- * ```js
1565
- * let numOfElements = await I.grabNumberOfVisibleElements('p');
1566
- * ```
1567
- *
1568
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
1569
- * @returns {Promise<number>} number of visible elements
1570
- */
1571
- async grabNumberOfVisibleElements(locator) {
1572
- if (this.isWeb) return super.grabNumberOfVisibleElements(locator);
1573
- return super.grabNumberOfVisibleElements(parseLocator.call(this, locator));
1574
- }
1575
-
1576
- /**
1577
- * Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1578
- *
1579
- * Retrieves an attribute from an element located by CSS or XPath and returns it to test.
1580
- * Resumes test execution, so **should be used inside async with `await`** operator.
1581
- * If more than one element is found - attribute of first element is returned.
1582
- *
1583
- * ```js
1584
- * let hint = await I.grabAttributeFrom('#tooltip', 'title');
1585
- * ```
1586
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1587
- * @param {string} attr attribute name.
1588
- * @returns {Promise<string>} attribute value
1589
- *
1590
- */
1591
- async grabAttributeFrom(locator, attr) {
1592
- if (this.isWeb) return super.grabAttributeFrom(locator, attr);
1593
- return super.grabAttributeFrom(parseLocator.call(this, locator), attr);
1594
- }
1595
-
1596
- /**
1597
- * Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1598
- * Retrieves an array of attributes from elements located by CSS or XPath and returns it to test.
1599
- * Resumes test execution, so **should be used inside async with `await`** operator.
1600
- *
1601
- * ```js
1602
- * let hints = await I.grabAttributeFromAll('.tooltip', 'title');
1603
- * ```
1604
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1605
- * @param {string} attr attribute name.
1606
- * @returns {Promise<string[]>} attribute value
1607
- *
1608
- */
1609
- async grabAttributeFromAll(locator, attr) {
1610
- if (this.isWeb) return super.grabAttributeFromAll(locator, attr);
1611
- return super.grabAttributeFromAll(parseLocator.call(this, locator), attr);
1612
- }
1613
-
1614
- /**
1615
- * Retrieves an array of value from a form located by CSS or XPath and returns it to test.
1616
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1617
- *
1618
- * ```js
1619
- * let inputs = await I.grabValueFromAll('//form/input');
1620
- * ```
1621
- * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
1622
- * @returns {Promise<string[]>} attribute value
1623
- *
1624
- *
1625
- */
1626
- async grabValueFromAll(locator) {
1627
- if (this.isWeb) return super.grabValueFromAll(locator);
1628
- return super.grabValueFromAll(parseLocator.call(this, locator));
1629
- }
1630
-
1631
- /**
1632
- * Retrieves a value from a form element located by CSS or XPath and returns it to test.
1633
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1634
- * If more than one element is found - value of first element is returned.
1635
- *
1636
- * ```js
1637
- * let email = await I.grabValueFrom('input[name=email]');
1638
- * ```
1639
- * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
1640
- * @returns {Promise<string>} attribute value
1641
- *
1642
- *
1643
- */
1644
- async grabValueFrom(locator) {
1645
- if (this.isWeb) return super.grabValueFrom(locator);
1646
- return super.grabValueFrom(parseLocator.call(this, locator));
1647
- }
1648
-
1649
- /**
1650
- * Saves a screenshot to ouput folder (set in codecept.conf.ts or codecept.conf.js).
1651
- * Filename is relative to output folder.
1652
- *
1653
- * ```js
1654
- * I.saveScreenshot('debug.png');
1655
- * ```
1656
- *
1657
- * @param {string} fileName file name to save.
1658
- * @return {Promise<void>}
1659
- */
1660
- async saveScreenshot(fileName) {
1661
- return super.saveScreenshot(fileName, false);
1662
- }
1663
-
1664
- /**
1665
- * Scroll element into viewport.
1666
- *
1667
- * ```js
1668
- * I.scrollIntoView('#submit');
1669
- * I.scrollIntoView('#submit', true);
1670
- * I.scrollIntoView('#submit', { behavior: "smooth", block: "center", inline: "center" });
1671
- * ```
1672
- *
1673
- * @param {LocatorOrString} locator located by CSS|XPath|strict locator.
1674
- * @param {ScrollIntoViewOptions} scrollIntoViewOptions see https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView.
1675
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1676
- *
1677
- *
1678
- * Supported only for web testing
1679
- */
1680
- async scrollIntoView(locator, scrollIntoViewOptions) {
1681
- if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions);
1682
- }
1683
-
1684
- /**
1685
- * Verifies that the specified checkbox is checked.
1686
- *
1687
- * ```js
1688
- * I.seeCheckboxIsChecked('Agree');
1689
- * I.seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms
1690
- * I.seeCheckboxIsChecked({css: '#signup_form input[type=checkbox]'});
1691
- * ```
1692
- *
1693
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1694
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1695
- *
1696
- *
1697
- */
1698
- async seeCheckboxIsChecked(field) {
1699
- if (this.isWeb) return super.seeCheckboxIsChecked(field);
1700
- return super.seeCheckboxIsChecked(parseLocator.call(this, field));
1701
- }
1702
-
1703
- /**
1704
- * Checks that a given Element is visible
1705
- * Element is located by CSS or XPath.
1706
- *
1707
- * ```js
1708
- * I.seeElement('#modal');
1709
- * ```
1710
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
1711
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1712
- *
1713
- *
1714
- */
1715
- async seeElement(locator) {
1716
- if (this.isWeb) return super.seeElement(locator);
1717
- return super.seeElement(parseLocator.call(this, locator));
1718
- }
1719
-
1720
- /**
1721
- * Checks that the given input field or textarea equals to given value.
1722
- * For fuzzy locators, fields are matched by label text, the "name" attribute, CSS, and XPath.
1723
- *
1724
- * ```js
1725
- * I.seeInField('Username', 'davert');
1726
- * I.seeInField({css: 'form textarea'},'Type your comment here');
1727
- * I.seeInField('form input[type=hidden]','hidden_value');
1728
- * I.seeInField('#searchform input','Search');
1729
- * ```
1730
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1731
- * @param {string} value value to check.
1732
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1733
- *
1734
- *
1735
- */
1736
- async seeInField(field, value) {
1737
- if (this.isWeb) return super.seeInField(field, value);
1738
- return super.seeInField(parseLocator.call(this, field), value);
1739
- }
1740
-
1741
- /**
1742
- * Checks that a page contains a visible text.
1743
- * Use context parameter to narrow down the search.
1744
- *
1745
- * ```js
1746
- * I.see('Welcome'); // text welcome on a page
1747
- * I.see('Welcome', '.content'); // text inside .content div
1748
- * I.see('Register', {css: 'form.register'}); // use strict locator
1749
- * ```
1750
- * @param {string} text expected on page.
1751
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS|Xpath|strict locator in which to search for text.
1752
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1753
- *
1754
- *
1755
- */
1756
- async see(text, context) {
1757
- if (this.isWeb) return super.see(text, context);
1758
- return super.see(text, parseLocator.call(this, context));
1759
- }
1760
-
1761
- /**
1762
- * Selects an option in a drop-down select.
1763
- * Field is searched by label | name | CSS | XPath.
1764
- * Option is selected by visible text or by value.
1765
- *
1766
- * ```js
1767
- * I.selectOption('Choose Plan', 'Monthly'); // select by label
1768
- * I.selectOption('subscription', 'Monthly'); // match option by text
1769
- * I.selectOption('subscription', '0'); // or by value
1770
- * I.selectOption('//form/select[@name=account]','Premium');
1771
- * I.selectOption('form select[name=account]', 'Premium');
1772
- * I.selectOption({css: 'form select[name=account]'}, 'Premium');
1773
- * ```
1774
- *
1775
- * Provide an array for the second argument to select multiple options.
1776
- *
1777
- * ```js
1778
- * I.selectOption('Which OS do you use?', ['Android', 'iOS']);
1779
- * ```
1780
- * @param {LocatorOrString} select field located by label|name|CSS|XPath|strict locator.
1781
- * @param {string|Array<*>} option visible text or value of option.
1782
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1783
- *
1784
- *
1785
- * Supported only for web testing
1786
- */
1787
- async selectOption(select, option) {
1788
- if (this.isWeb) return super.selectOption(select, option);
1789
- throw new Error('Should be used only in Web context. In native context use \'click\' method instead');
1790
- }
1791
-
1792
- /**
1793
- * Waits for element to be present on page (by default waits for 1sec).
1794
- * Element can be located by CSS or XPath.
1795
- *
1796
- * ```js
1797
- * I.waitForElement('.btn.continue');
1798
- * I.waitForElement('.btn.continue', 5); // wait for 5 secs
1799
- * ```
1800
- *
1801
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1802
- * @param {number} [sec] (optional, `1` by default) time in seconds to wait
1803
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1804
- *
1805
- *
1806
- */
1807
- async waitForElement(locator, sec = null) {
1808
- if (this.isWeb) return super.waitForElement(locator, sec);
1809
- return super.waitForElement(parseLocator.call(this, locator), sec);
1810
- }
1811
-
1812
- /**
1813
- * Waits for an element to become visible on a page (by default waits for 1sec).
1814
- * Element can be located by CSS or XPath.
1815
- *
1816
- * ```js
1817
- * I.waitForVisible('#popup');
1818
- * ```
1819
- *
1820
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1821
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1822
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1823
- *
1824
- *
1825
- */
1826
- async waitForVisible(locator, sec = null) {
1827
- if (this.isWeb) return super.waitForVisible(locator, sec);
1828
- return super.waitForVisible(parseLocator.call(this, locator), sec);
1829
- }
1830
-
1831
- /**
1832
- * Waits for an element to be removed or become invisible on a page (by default waits for 1sec).
1833
- * Element can be located by CSS or XPath.
1834
- *
1835
- * ```js
1836
- * I.waitForInvisible('#popup');
1837
- * ```
1838
- *
1839
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1840
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1841
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1842
- *
1843
- *
1844
- */
1845
- async waitForInvisible(locator, sec = null) {
1846
- if (this.isWeb) return super.waitForInvisible(locator, sec);
1847
- return super.waitForInvisible(parseLocator.call(this, locator), sec);
1848
- }
1849
-
1850
- /**
1851
- * Waits for a text to appear (by default waits for 1sec).
1852
- * Element can be located by CSS or XPath.
1853
- * Narrow down search results by providing context.
1854
- *
1855
- * ```js
1856
- * I.waitForText('Thank you, form has been submitted');
1857
- * I.waitForText('Thank you, form has been submitted', 5, '#modal');
1858
- * ```
1859
- *
1860
- * @param {string }text to wait for.
1861
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1862
- * @param {CodeceptJS.LocatorOrString} [context] (optional) element located by CSS|XPath|strict locator.
1863
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1864
- *
1865
- *
1866
- */
1867
- async waitForText(text, sec = null, context = null) {
1868
- if (this.isWeb) return super.waitForText(text, sec, context);
1869
- return super.waitForText(text, sec, parseLocator.call(this, context));
1870
- }
1871
- }
1872
-
1873
- function parseLocator(locator) {
1874
- if (!locator) return null;
1875
-
1876
- if (typeof locator === 'object') {
1877
- if (locator.web && this.isWeb) {
1878
- return parseLocator.call(this, locator.web);
1879
- }
1880
-
1881
- if (locator.android && this.platform === 'android') {
1882
- if (typeof locator.android === 'string') {
1883
- return parseLocator.call(this, locator.android);
1884
- }
1885
- // The locator is an Android DataMatcher or ViewMatcher locator so return as is
1886
- return locator.android;
1887
- }
1888
-
1889
- if (locator.ios && this.platform === 'ios') {
1890
- return parseLocator.call(this, locator.ios);
1891
- }
1892
- }
1893
-
1894
- if (typeof locator === 'string') {
1895
- if (locator[0] === '~') return locator;
1896
- if (locator.substr(0, 2) === '//') return locator;
1897
- if (locator[0] === '#' && !this.isWeb) {
1898
- // hook before webdriverio supports native # locators
1899
- return parseLocator.call(this, { id: locator.slice(1) });
1900
- }
1901
-
1902
- if (this.platform === 'android' && !this.isWeb) {
1903
- const isNativeLocator = /^\-?android=?/.exec(locator);
1904
- return isNativeLocator
1905
- ? locator
1906
- : `android=new UiSelector().text("${locator}")`;
1907
- }
1908
- }
1909
-
1910
- locator = new Locator(locator, 'xpath');
1911
- if (locator.type === 'css' && !this.isWeb) throw new Error('Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id');
1912
- if (locator.type === 'name' && !this.isWeb) throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id");
1913
- if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return `//*[@resource-id='${locator.value}']`;
1914
- return locator.simplify();
1915
- }
1916
-
1917
- // in the end of a file
1918
- function onlyForApps(expectedPlatform) {
1919
- const stack = new Error().stack || '';
1920
- const re = /Appium.(\w+)/g;
1921
- const caller = stack.split('\n')[2].trim();
1922
- const m = re.exec(caller);
1923
-
1924
- if (!m) {
1925
- throw new Error(`Invalid caller ${caller}`);
1926
- }
1927
-
1928
- const callerName = m[1] || m[2];
1929
- if (!expectedPlatform) {
1930
- if (!this.platform) {
1931
- throw new Error(`${callerName} method can be used only with apps`);
1932
- }
1933
- } else if (this.platform !== expectedPlatform.toLowerCase()) {
1934
- throw new Error(`${callerName} method can be used only with ${expectedPlatform} apps`);
1935
- }
1936
- }
1937
-
1938
- module.exports = Appium;