Skip to content

Commit 3e5434d

Browse files
authored
Merge branch '4.x' into clone-fix-appium-touchPerform
2 parents 51f6aa2 + d18924b commit 3e5434d

16 files changed

Lines changed: 380 additions & 26 deletions

File tree

.github/workflows/appium_Android.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: Appium Tests - Android
22

33
on:
44
push:
5+
branches:
6+
- 4.x
7+
pull_request:
58
branches:
69
- 4.x
710
- clone-fix-appium-touchPerform

lib/command/init.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,12 @@ export default async function (initPath, options = {}) {
245245
}
246246

247247
const tsconfig = {
248-
'ts-node': {
249-
files: true,
250-
},
251248
compilerOptions: {
252-
target: 'es2018',
253-
lib: ['es2018', 'DOM'],
249+
target: 'ES2022',
250+
lib: ['ES2022', 'DOM'],
254251
esModuleInterop: true,
255-
module: 'commonjs',
252+
module: 'ESNext',
253+
moduleResolution: 'bundler',
256254
strictNullChecks: false,
257255
types: ['codeceptjs', 'node'],
258256
declaration: true,

lib/element/WebElement.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@ class WebElement {
1515
_detectHelperType(helper) {
1616
if (!helper) return 'unknown'
1717

18-
const className = helper.constructor.name
19-
if (className === 'Playwright') return 'playwright'
20-
if (className === 'WebDriver') return 'webdriver'
21-
if (className === 'Puppeteer') return 'puppeteer'
18+
let ctor = helper.constructor
19+
while (ctor && ctor.name) {
20+
if (ctor.name === 'Playwright') return 'playwright'
21+
if (ctor.name === 'WebDriver') return 'webdriver'
22+
if (ctor.name === 'Puppeteer') return 'puppeteer'
23+
ctor = Object.getPrototypeOf(ctor)
24+
}
2225

2326
return 'unknown'
2427
}

lib/helper/Appium.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,13 @@ class Appium extends Webdriver {
179179
super(config)
180180

181181
this.isRunning = false
182-
this.appiumV2 = config.appiumV2 || true
182+
this.appiumV2 = config.appiumV2 !== false
183183
this.axios = axios.create()
184184

185-
if (!config.appiumV2) {
186-
console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Appium 2.x is used by default.')
185+
if (this.appiumV2 === false) {
186+
console.log('Appium 1.x is no longer maintained by the Appium team (since 2022-01-01).')
187187
console.log('More info: https://bit.ly/appium-v2-migration')
188-
console.log('This Appium 1.x support will be removed in next major release.')
188+
console.log('Appium 1.x support will be removed in the next major CodeceptJS release.')
189189
}
190190
}
191191

@@ -220,6 +220,9 @@ class Appium extends Webdriver {
220220
deprecationWarnings: false,
221221
restart: true,
222222
manualStart: false,
223+
// Mobile emulator/device cold starts (esp. on cloud grids like Sauce Labs)
224+
// routinely exceed webdriverio's 2-minute default. Bump to 5 min.
225+
connectionRetryTimeout: 300000, // ms
223226
timeouts: {
224227
script: 0, // ms
225228
},
@@ -331,6 +334,24 @@ class Appium extends Webdriver {
331334
}
332335
this.$$ = this.browser.$$.bind(this.browser)
333336

337+
// wdio v9 implements `element.isDisplayed()` for non-mobile contexts by
338+
// injecting a JS visibility check via POST /execute/sync. Appium's native
339+
// context cannot execute JS, so each call logs an ugly
340+
// "Method is not implemented" ERROR before the existing catch swallows it.
341+
// Short-circuit isDisplayed to `true` while in native context so the
342+
// request is never issued (matches existing _isDisplayedSafe semantics).
343+
const helper = this
344+
if (typeof this.browser.overwriteCommand === 'function') {
345+
this.browser.overwriteCommand(
346+
'isDisplayed',
347+
async function (origFn, ...args) {
348+
if (helper.isWeb) return origFn.call(this, ...args)
349+
return true
350+
},
351+
true,
352+
)
353+
}
354+
334355
this.isRunning = true
335356
if (this.options.timeouts && this.isWeb) {
336357
await this.defineTimeout(this.options.timeouts)

lib/helper/WebDriver.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1264,7 +1264,7 @@ class WebDriver extends Helper {
12641264
const elem = selectElement(res, field, this)
12651265
highlightActiveElement.call(this, elem)
12661266

1267-
if (await fillRichEditor(this, elem, value)) {
1267+
if (this.isWeb !== false && await fillRichEditor(this, elem, value)) {
12681268
return
12691269
}
12701270

lib/mocha/asyncWrapper.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export function setup(suite) {
199199
recorder.startUnlessRunning()
200200
import('./test.js').then(testModule => {
201201
const { enhanceMochaTest } = testModule.default || testModule
202-
event.emit(event.test.before, enhanceMochaTest(suite?.ctx?.currentTest))
202+
event.emit(event.test.before, enhanceMochaTest(suite?.ctx?.currentTest ?? suite?.currentTest))
203203
recorder.add(() => doneFn())
204204
})
205205
}
@@ -211,7 +211,7 @@ export function teardown(suite) {
211211
recorder.startUnlessRunning()
212212
import('./test.js').then(testModule => {
213213
const { enhanceMochaTest } = testModule.default || testModule
214-
event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest))
214+
event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest ?? suite?.currentTest))
215215
recorder.add(() => doneFn())
216216
})
217217
}

lib/mocha/gherkin.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,12 @@ const gherkinParser = (text, file) => {
4242
suite.file = file
4343
suite.timeout(0)
4444

45-
suite.beforeEach('codeceptjs.before', function () {
46-
// In Mocha, 'this' refers to the current test in beforeEach/afterEach hooks
47-
setup(this)(() => {})
45+
suite.beforeEach('codeceptjs.before', function (done) {
46+
// In Mocha, 'this' is the hook Context; currentTest is the running scenario
47+
setup(this)(done)
4848
})
49-
suite.afterEach('codeceptjs.after', function () {
50-
// In Mocha, 'this' refers to the current test in beforeEach/afterEach hooks
51-
teardown(this)(() => {})
49+
suite.afterEach('codeceptjs.after', function (done) {
50+
teardown(this)(done)
5251
})
5352
suite.beforeAll('codeceptjs.beforeSuite', suiteSetup(suite))
5453
suite.afterAll('codeceptjs.afterSuite', suiteTeardown(suite))

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"acorn": "8.15.0",
101101
"ai": "^6.0.43",
102102
"arrify": "3.0.0",
103-
"axios": "1.13.2",
103+
"axios": "1.16.1",
104104
"chalk": "4.1.2",
105105
"cheerio": "^1.0.0",
106106
"chokidar": "^5.0.0",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@Playwright @Puppeteer @WebDriverIO @bdd @hookCapture
2+
Feature: Before hooks receive the real scenario
3+
4+
Background:
5+
Given I opened website
6+
7+
@scenarioHook
8+
Scenario: Before hook captures scenario metadata
9+
Then the Before hook should have captured this scenario

test/acceptance/gherkin/steps.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,71 @@
11
const I = actor();
2+
const event = codeceptjs.event;
3+
4+
const capturedScenarios = [];
5+
6+
Before(test => {
7+
capturedScenarios.push({
8+
phase: 'Before-hook',
9+
title: test && test.title,
10+
tags: test && Array.isArray(test.tags) ? [...test.tags] : null,
11+
});
12+
});
13+
14+
After(test => {
15+
capturedScenarios.push({
16+
phase: 'After-hook',
17+
title: test && test.title,
18+
tags: test && Array.isArray(test.tags) ? [...test.tags] : null,
19+
});
20+
});
21+
22+
event.dispatcher.on(event.test.before, test => {
23+
capturedScenarios.push({
24+
phase: 'event.test.before',
25+
title: test && test.title,
26+
tags: test && Array.isArray(test.tags) ? [...test.tags] : null,
27+
});
28+
});
29+
30+
event.dispatcher.on(event.test.after, test => {
31+
capturedScenarios.push({
32+
phase: 'event.test.after',
33+
title: test && test.title,
34+
tags: test && Array.isArray(test.tags) ? [...test.tags] : null,
35+
});
36+
});
237

338
Given('I opened website', () => {
439
// From "gherkin/basic.feature" {"line":8,"column":5}
540
I.amOnPage('/');
641
});
742

43+
Then('the Before hook should have captured this scenario', () => {
44+
const captureSnapshot = JSON.stringify(capturedScenarios, null, 2);
45+
46+
const matchesScenario = entry =>
47+
entry.tags && entry.tags.includes('@scenarioHook') && entry.tags.includes('@hookCapture');
48+
49+
const bddBefore = capturedScenarios.find(
50+
entry => entry.phase === 'Before-hook' && matchesScenario(entry),
51+
);
52+
const eventBefore = capturedScenarios.find(
53+
entry => entry.phase === 'event.test.before' && matchesScenario(entry),
54+
);
55+
56+
if (!bddBefore) throw new Error(`BDD Before() did not fire for @scenarioHook. Captured:\n${captureSnapshot}`);
57+
if (!eventBefore) throw new Error(`event.test.before did not fire with real test. Captured:\n${captureSnapshot}`);
58+
59+
for (const entry of [bddBefore, eventBefore]) {
60+
if (!entry.title || entry.title === '...') {
61+
throw new Error(`Placeholder title in ${entry.phase}: ${JSON.stringify(entry)}`);
62+
}
63+
if (!entry.title.includes('Before hook captures scenario metadata')) {
64+
throw new Error(`Unexpected title in ${entry.phase}: ${JSON.stringify(entry)}`);
65+
}
66+
}
67+
});
68+
869
When('go to {string} page', (url) => {
970
// From "gherkin/basic.feature" {"line":9,"column":5}
1071
I.amOnPage(url);

0 commit comments

Comments
 (0)