Skip to content

Commit d2049ed

Browse files
author
DavertMik
committed
Merge branch '3.x' of github.com:codeceptjs/CodeceptJS into 3.x
2 parents 6edc423 + c503ea2 commit d2049ed

File tree

13 files changed

+468
-270
lines changed

13 files changed

+468
-270
lines changed

.github/workflows/appiumV2_Android.yml

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ env:
99
CI: true
1010
# Force terminal colors. @see https://www.npmjs.com/package/colors
1111
FORCE_COLOR: 1
12+
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
13+
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
1214

1315
jobs:
1416
appium:
@@ -20,17 +22,23 @@ jobs:
2022
test-suite: ['other', 'quick']
2123

2224
steps:
23-
- uses: actions/checkout@v4
24-
- name: Use Node.js ${{ matrix.node-version }}
25-
uses: actions/setup-node@v4
26-
with:
27-
node-version: ${{ matrix.node-version }}
28-
- run: npm i --force
29-
env:
30-
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true
31-
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
32-
- run: "npm run test:appium-${{ matrix.test-suite }}"
33-
env: # Or as an environment variable
34-
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
35-
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
25+
- uses: actions/checkout@v4
3626

27+
- name: Use Node.js ${{ matrix.node-version }}
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: ${{ matrix.node-version }}
31+
32+
- run: npm i
33+
env:
34+
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true
35+
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
36+
37+
- name: Upload APK to Sauce Labs
38+
run: |
39+
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \
40+
--location --request POST 'https://api.us-west-1.saucelabs.com/v1/storage/upload' \
41+
--form 'payload=@test/data/mobile/selendroid-test-app-0.17.0.apk' \
42+
--form 'name="selendroid-test-app-0.17.0.apk"'
43+
44+
- run: 'npm run test:appium-${{ matrix.test-suite }}'

.github/workflows/appiumV2_iOS.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ env:
99
CI: true
1010
# Force terminal colors. @see https://www.npmjs.com/package/colors
1111
FORCE_COLOR: 1
12+
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
13+
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
1214

1315
jobs:
1416
appium:
@@ -29,8 +31,12 @@ jobs:
2931
env:
3032
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true
3133
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
32-
- run: "npm run test:ios:appium-${{ matrix.test-suite }}"
33-
env: # Or as an environment variable
34-
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
35-
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
3634

35+
- name: Upload APK to Sauce Labs
36+
run: |
37+
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \
38+
--location --request POST 'https://api.us-west-1.saucelabs.com/v1/storage/upload' \
39+
--form 'payload=@test/data/mobile/TestApp-iphonesimulator.zip' \
40+
--form 'name="TestApp-iphonesimulator.zip"'
41+
42+
- run: 'npm run test:ios:appium-${{ matrix.test-suite }}'

docs/helpers/Appium.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,19 +550,10 @@ Hide the keyboard.
550550
```js
551551
// taps outside to hide keyboard per default
552552
I.hideDeviceKeyboard();
553-
I.hideDeviceKeyboard('tapOutside');
554-
555-
// or by pressing key
556-
I.hideDeviceKeyboard('pressKey', 'Done');
557553
```
558554

559555
Appium: support Android and iOS
560556

561-
#### Parameters
562-
563-
* `strategy` **(`"tapOutside"` | `"pressKey"`)?** Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
564-
* `key` **[string][5]?** Optional key
565-
566557
### sendDeviceKeyEvent
567558

568559
Send a key event to the device.

lib/effects.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const recorder = require('./recorder')
2+
const { debug } = require('./output')
3+
const store = require('./store')
4+
5+
/**
6+
* @module hopeThat
7+
*
8+
* `hopeThat` is a utility function for CodeceptJS tests that allows for soft assertions.
9+
* It enables conditional assertions without terminating the test upon failure.
10+
* This is particularly useful in scenarios like A/B testing, handling unexpected elements,
11+
* or performing multiple assertions where you want to collect all results before deciding
12+
* on the test outcome.
13+
*
14+
* ## Use Cases
15+
*
16+
* - **Multiple Conditional Assertions**: Perform several assertions and evaluate all their outcomes together.
17+
* - **A/B Testing**: Handle different variants in A/B tests without failing the entire test upon one variant's failure.
18+
* - **Unexpected Elements**: Manage elements that may or may not appear, such as "Accept Cookie" banners.
19+
*
20+
* ## Examples
21+
*
22+
* ### Multiple Conditional Assertions
23+
*
24+
* Add the assertion library:
25+
* ```js
26+
* const assert = require('assert');
27+
* const { hopeThat } = require('codeceptjs/effects');
28+
* ```
29+
*
30+
* Use `hopeThat` with assertions:
31+
* ```js
32+
* const result1 = await hopeThat(() => I.see('Hello, user'));
33+
* const result2 = await hopeThat(() => I.seeElement('.welcome'));
34+
* assert.ok(result1 && result2, 'Assertions were not successful');
35+
* ```
36+
*
37+
* ### Optional Click
38+
*
39+
* ```js
40+
* const { hopeThat } = require('codeceptjs/effects');
41+
*
42+
* I.amOnPage('/');
43+
* await hopeThat(() => I.click('Agree', '.cookies'));
44+
* ```
45+
*
46+
* Performs a soft assertion within CodeceptJS tests.
47+
*
48+
* This function records the execution of a callback containing assertion logic.
49+
* If the assertion fails, it logs the failure without stopping the test execution.
50+
* It is useful for scenarios where multiple assertions are performed, and you want
51+
* to evaluate all outcomes before deciding on the test result.
52+
*
53+
* ## Usage
54+
*
55+
* ```js
56+
* const result = await hopeThat(() => I.see('Welcome'));
57+
*
58+
* // If the text "Welcome" is on the page, result => true
59+
* // If the text "Welcome" is not on the page, result => false
60+
* ```
61+
*
62+
* @async
63+
* @function hopeThat
64+
* @param {Function} callback - The callback function containing the soft assertion logic.
65+
* @returns {Promise<boolean | any>} - Resolves to `true` if the assertion is successful, or `false` if it fails.
66+
*
67+
* @example
68+
* // Multiple Conditional Assertions
69+
* const assert = require('assert');
70+
* const { hopeThat } = require('codeceptjs/effects');
71+
*
72+
* const result1 = await hopeThat(() => I.see('Hello, user'));
73+
* const result2 = await hopeThat(() => I.seeElement('.welcome'));
74+
* assert.ok(result1 && result2, 'Assertions were not successful');
75+
*
76+
* @example
77+
* // Optional Click
78+
* const { hopeThat } = require('codeceptjs/effects');
79+
*
80+
* I.amOnPage('/');
81+
* await hopeThat(() => I.click('Agree', '.cookies'));
82+
*/
83+
async function hopeThat(callback) {
84+
if (store.dryRun) return
85+
const sessionName = 'hopeThat'
86+
87+
let result = false
88+
return recorder.add(
89+
'hopeThat',
90+
() => {
91+
recorder.session.start(sessionName)
92+
store.hopeThat = true
93+
callback()
94+
recorder.add(() => {
95+
result = true
96+
recorder.session.restore(sessionName)
97+
return result
98+
})
99+
recorder.session.catch(err => {
100+
result = false
101+
const msg = err.inspect ? err.inspect() : err.toString()
102+
debug(`Unsuccessful assertion > ${msg}`)
103+
recorder.session.restore(sessionName)
104+
return result
105+
})
106+
return recorder.add(
107+
'result',
108+
() => {
109+
store.hopeThat = undefined
110+
return result
111+
},
112+
true,
113+
false,
114+
)
115+
},
116+
false,
117+
false,
118+
)
119+
}
120+
121+
module.exports = {
122+
hopeThat,
123+
}

lib/helper/Appium.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ class Appium extends Webdriver {
386386
_buildAppiumEndpoint() {
387387
const { protocol, port, hostname, path } = this.browser.options
388388
// Build path to Appium REST API endpoint
389-
return `${protocol}://${hostname}:${port}${path}`
389+
return `${protocol}://${hostname}:${port}${path}/session/${this.browser.sessionId}`
390390
}
391391

392392
/**
@@ -602,7 +602,7 @@ class Appium extends Webdriver {
602602

603603
return this.axios({
604604
method: 'post',
605-
url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/device/remove_app`,
605+
url: `${this._buildAppiumEndpoint()}/appium/device/remove_app`,
606606
data: { appId, bundleId },
607607
})
608608
}
@@ -619,7 +619,7 @@ class Appium extends Webdriver {
619619
onlyForApps.call(this)
620620
return this.axios({
621621
method: 'post',
622-
url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/app/reset`,
622+
url: `${this._buildAppiumEndpoint()}/appium/app/reset`,
623623
})
624624
}
625625

@@ -693,7 +693,7 @@ class Appium extends Webdriver {
693693

694694
const res = await this.axios({
695695
method: 'get',
696-
url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
696+
url: `${this._buildAppiumEndpoint()}/orientation`,
697697
})
698698

699699
const currentOrientation = res.data.value
@@ -717,7 +717,7 @@ class Appium extends Webdriver {
717717

718718
return this.axios({
719719
method: 'post',
720-
url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
720+
url: `${this._buildAppiumEndpoint()}/orientation`,
721721
data: { orientation },
722722
})
723723
}
@@ -956,21 +956,19 @@ class Appium extends Webdriver {
956956
* ```js
957957
* // taps outside to hide keyboard per default
958958
* I.hideDeviceKeyboard();
959-
* I.hideDeviceKeyboard('tapOutside');
960-
*
961-
* // or by pressing key
962-
* I.hideDeviceKeyboard('pressKey', 'Done');
963959
* ```
964960
*
965961
* Appium: support Android and iOS
966962
*
967-
* @param {'tapOutside' | 'pressKey'} [strategy] Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
968-
* @param {string} [key] Optional key
969963
*/
970-
async hideDeviceKeyboard(strategy, key) {
964+
async hideDeviceKeyboard() {
971965
onlyForApps.call(this)
972-
strategy = strategy || 'tapOutside'
973-
return this.browser.hideKeyboard(strategy, key)
966+
967+
return this.axios({
968+
method: 'post',
969+
url: `${this._buildAppiumEndpoint()}/appium/device/hide_keyboard`,
970+
data: {},
971+
})
974972
}
975973

976974
/**
@@ -1046,7 +1044,13 @@ class Appium extends Webdriver {
10461044
* @param {*} locator
10471045
*/
10481046
async tap(locator) {
1049-
return this.makeTouchAction(locator, 'tap')
1047+
const { elementId } = await this.browser.$(parseLocator.call(this, locator))
1048+
1049+
return this.axios({
1050+
method: 'post',
1051+
url: `${this._buildAppiumEndpoint()}/element/${elementId}/click`,
1052+
data: {},
1053+
})
10501054
}
10511055

10521056
/**
@@ -1493,7 +1497,14 @@ class Appium extends Webdriver {
14931497
*/
14941498
async click(locator, context) {
14951499
if (this.isWeb) return super.click(locator, context)
1496-
return super.click(parseLocator.call(this, locator), parseLocator.call(this, context))
1500+
1501+
const { elementId } = await this.browser.$(parseLocator.call(this, locator), parseLocator.call(this, context))
1502+
1503+
return this.axios({
1504+
method: 'post',
1505+
url: `${this._buildAppiumEndpoint()}/element/${elementId}/click`,
1506+
data: {},
1507+
})
14971508
}
14981509

14991510
/**

lib/listener/emptyRun.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const figures = require('figures')
22
const Container = require('../container')
33
const event = require('../event')
44
const output = require('../output')
5+
const { searchWithFusejs } = require('../utils')
56

67
module.exports = function () {
78
let isEmptyRun = true
@@ -15,8 +16,6 @@ module.exports = function () {
1516
const mocha = Container.mocha()
1617

1718
if (mocha.options.grep) {
18-
const Fuse = require('fuse.js')
19-
2019
output.print()
2120
output.print('No tests found by pattern: ' + mocha.options.grep)
2221

@@ -27,14 +26,12 @@ module.exports = function () {
2726
})
2827
})
2928

30-
const fuse = new Fuse(allTests, {
29+
const results = searchWithFusejs(allTests, mocha.options.grep.toString(), {
3130
includeScore: true,
3231
threshold: 0.6,
3332
caseSensitive: false,
3433
})
3534

36-
const results = fuse.search(mocha.options.grep.toString())
37-
3835
if (results.length > 0) {
3936
output.print()
4037
output.print('Maybe you wanted to run one of these tests?')

lib/pause.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ const colors = require('chalk')
22
const readline = require('readline')
33
const ora = require('ora-classic')
44
const debug = require('debug')('codeceptjs:pause')
5-
const Fuse = require('fuse.js')
65

76
const container = require('./container')
87
const history = require('./history')
@@ -11,7 +10,7 @@ const aiAssistant = require('./ai')
1110
const recorder = require('./recorder')
1211
const event = require('./event')
1312
const output = require('./output')
14-
const { methodsOfObject } = require('./utils')
13+
const { methodsOfObject, searchWithFusejs } = require('./utils')
1514

1615
// npm install colors
1716
let rl
@@ -218,15 +217,12 @@ function completer(line) {
218217
return [completions, line]
219218
}
220219

221-
// Initialize Fuse with completions
222-
const fuse = new Fuse(completions, {
220+
// Search using Fuse.js
221+
const searchResults = searchWithFusejs(completions, line, {
223222
threshold: 0.3,
224223
distance: 100,
225224
minMatchCharLength: 1,
226225
})
227-
228-
// Search using Fuse.js
229-
const searchResults = fuse.search(line)
230226
const hits = searchResults.map(result => result.item)
231227

232228
return [hits, line]

0 commit comments

Comments
 (0)