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