codeceptjs 4.2.1 → 4.2.2

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.
@@ -115,6 +115,22 @@ export const config = {
115
115
 
116
116
  See [Alternative Browser Engines](/alternative-browsers) for running an existing Playwright suite on Lightpanda.
117
117
 
118
+ ## Moli (experimental)
119
+
120
+ Moli is a headless browser for automation that runs your app's JavaScript in V8 and skips layout and painting. Install it following the [official instructions](https://github.com/lexmount/moli#direct-installation), then enable the helper:
121
+
122
+ ```js
123
+ export const config = {
124
+ helpers: {
125
+ Moli: {
126
+ url: 'http://localhost:3000',
127
+ },
128
+ },
129
+ }
130
+ ```
131
+
132
+ See [Alternative Browser Engines](/alternative-browsers) for running an existing Playwright suite on Moli.
133
+
118
134
  ## Appium (mobile)
119
135
 
120
136
  Native iOS and Android testing. Appium speaks the WebDriver protocol, so CodeceptJS drives it through `webdriverio`:
@@ -134,6 +150,8 @@ appium
134
150
 
135
151
  You'll also need the Android SDK and an emulator or device, or Xcode and a simulator or device (or a device-cloud account). Set `platformName`, `deviceName`, and `app` in the helper config — see [Mobile Testing](/mobile).
136
152
 
153
+ For mobile tests without an Appium server, see the experimental [Mobilewright helper](/mobilewright): it drives devices directly via adb/mobilecli and reads the accessibility tree.
154
+
137
155
  ## API testing
138
156
 
139
157
  REST and GraphQL tests need no browser — the helpers are built in (they use `axios`, already a dependency):
package/docs/lint.md ADDED
@@ -0,0 +1,111 @@
1
+ ---
2
+ permalink: /lint
3
+ title: Linting
4
+ ---
5
+
6
+ # Linting
7
+
8
+ CodeceptJS tests have well-known smell patterns: fixed sleeps instead of waits, `if` branches that make tests non-deterministic, raw `fetch()` calls that bypass test steps and reports, plain-text passwords printed into logs, missing `await` on grabbers, and leftover `pause()` or `.only` from debugging sessions.
9
+
10
+ [`@codeceptjs/lint`](https://github.com/codeceptjs/lint) catches all of them statically. It reads tests, page objects and helpers as an AST — CodeceptJS itself doesn't need to be installed and no browser is started.
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ npx @codeceptjs/lint # files from codecept.conf.js in the current directory
16
+ npx @codeceptjs/lint tests/ pages/login.js # explicit paths
17
+ npx @codeceptjs/lint -r no-pause -r no-only # only these rules (or --rules no-pause,no-only)
18
+ npx @codeceptjs/lint rules # list all rules
19
+ npx @codeceptjs/lint --json # findings as JSON
20
+ npx @codeceptjs/lint -c e2e/codecept.conf.js # config in another directory
21
+ ```
22
+
23
+ Each finding is printed on one line:
24
+
25
+ ```
26
+ checkout_test.js:4:3 error no-fixed-wait I.wait() sleeps unconditionally. Wait for a condition: I.waitForElement / I.waitForText / I.see
27
+ ```
28
+
29
+ Exit codes: `0` — no errors (warnings allowed), `1` — errors found, `2` — bad input or a file that can't be parsed.
30
+
31
+ Without paths, the linter reads `codecept.conf.js` (or `.ts`, `.cjs`, `.mjs`) and lints files matched by `tests`, local files from `include` (page objects, steps file) and helpers loaded with `require`.
32
+
33
+ ## Rules
34
+
35
+ | Rule | Default | Detects |
36
+ | --- | --- | --- |
37
+ | `no-fixed-wait` | error | `I.wait(5)` with a number. Use `I.waitForElement`, `I.waitForText`, `I.see` |
38
+ | `no-sleep` | error | `setTimeout`, including `window.setTimeout`, `await new Promise(r => setTimeout(r, ms))` and `timers/promises`, outside helpers |
39
+ | `no-if-in-test` | error | `if` inside a Scenario or hook. Use `tryTo()` from `codeceptjs/effects` |
40
+ | `no-fetch` | error | `fetch()` outside helpers. Use the REST helper: `I.sendGetRequest`, `I.sendPostRequest` |
41
+ | `no-console` | warn | `console.*` outside helpers. Use `I.say()` |
42
+ | `no-only` | error on CI, warn locally | `Scenario.only`, `Feature.only`, `Data(...).only` |
43
+ | `no-pause` | error on CI, warn locally | `pause()` |
44
+ | `no-skip` | warn | `Scenario.skip`, `Feature.skip`, `xScenario`, `xFeature` |
45
+ | `secret-credentials` | error | `I.fillField` on a password, token, secret or API key field, or such a `process.env.*` passed to `I.*`, without `secret()` |
46
+ | `await-grab` | error | `I.grab*()` assigned or used without `await` |
47
+ | `no-actor-in-helper` | error | `I` inside a class extending `Helper`. Use `this.helpers[...]` |
48
+ | `raw-browser-in-test` | warn | `I.use*To`, `I.executeScript` in a Scenario. Move it into a helper or page object |
49
+
50
+ "On CI" means the `CI` environment variable is set, which every CI provider does. Locally `pause()` and `.only` stay warnings, so debugging stubs don't fail the lint.
51
+
52
+ ## Configuration
53
+
54
+ Set rule levels in a `lint` section of `codecept.conf.js`:
55
+
56
+ ```js
57
+ export const config = {
58
+ tests: './*_test.js',
59
+ lint: {
60
+ rules: { 'no-if-in-test': 'warn', 'raw-browser-in-test': 'off' },
61
+ },
62
+ }
63
+ ```
64
+
65
+ Levels are `error`, `warn` or `off`.
66
+
67
+ To allow a single case, suppress it inline — the rule id is required:
68
+
69
+ ```js
70
+ I.wait(1) // codeceptjs-lint-disable-line no-fixed-wait
71
+
72
+ // codeceptjs-lint-disable-next-line no-fixed-wait
73
+ I.wait(1)
74
+ ```
75
+
76
+ TypeScript is read with Node's type stripping (Node 22.13+). Files that use `enum` or `namespace` are reported as parse errors. A TypeScript config that imports `.js` paths of `.ts` files (the tsx/esbuild convention) can't be loaded by plain Node; if the project has `tsx` installed, the linter registers its loader automatically, otherwise pass paths explicitly.
77
+
78
+ ## CI
79
+
80
+ ```yaml
81
+ - run: npx @codeceptjs/lint
82
+ - run: npx codeceptjs run
83
+ ```
84
+
85
+ ## Blocking Bad Edits by an AI Agent
86
+
87
+ The linter can check an agent's edit before it lands. Add a `PreToolUse` hook to `.claude/settings.json`:
88
+
89
+ ```json
90
+ {
91
+ "hooks": {
92
+ "PreToolUse": [
93
+ {
94
+ "matcher": "Write|Edit|MultiEdit",
95
+ "hooks": [{ "type": "command", "command": "npx @codeceptjs/lint --hook claude" }]
96
+ }
97
+ ]
98
+ }
99
+ }
100
+ ```
101
+
102
+ The hook checks only files the config lints. It builds the file as it would look after the edit, and blocks the edit only when it adds a new error. Errors already in the file and warnings never block, so an agent can still add a `pause()` stub or touch a legacy test. If the hook fails, the edit is allowed. See [Agentic Testing](/agents/) for the full agent toolkit.
103
+
104
+ ## API
105
+
106
+ ```js
107
+ import Linter from '@codeceptjs/lint'
108
+
109
+ const linter = await Linter.load('codecept.conf.js')
110
+ for (const file of linter.files()) console.log(linter.lintFile(file))
111
+ ```
package/docs/mobile.md CHANGED
@@ -231,6 +231,7 @@ Accessibility id is recommended to use for locating element, as it rarely change
231
231
  * For [React Native for Android see our special guide](mobile-react-native-locators.md).
232
232
 
233
233
  > If you test React Native application, consider using [Detox helper](/detox) for faster tests.
234
+ > If you want mobile tests without an Appium server at all, consider the experimental [Mobilewright helper](/mobilewright).
234
235
 
235
236
  Add `~` prefix to search for element by its accessibility id:
236
237
 
@@ -0,0 +1,166 @@
1
+ ---
2
+ permalink: /mobilewright
3
+ title: Mobile Testing with Mobilewright
4
+ ---
5
+
6
+ > Warning! Mobilewright support in CodeceptJS is experimental. Please try it and help us to test it and improve it. [See Mobilewright helper repository](https://github.com/codeceptjs/mobilewright-helper).
7
+
8
+ [Appium](/mobile) is the default choice for mobile testing with CodeceptJS, but it requires a running Appium server and slow UI-dump based automation. [Mobilewright](https://mobilewright.dev) takes a different approach: it talks to simulators, emulators and real devices directly via adb (Android) and mobilecli (iOS), and reads the accessibility tree instead of taking UI snapshots.
9
+
10
+ **Pros** (vs Appium):
11
+
12
+ * no Appium server, no WebDriver — a device and `adb` is all you need
13
+ * fast startup: a full suite of 4 scenarios runs in about a minute, ~7-10s per test including app installation
14
+ * Playwright-like locators: `{ role: 'button', name: 'Sign In' }`, test ids, accessibility labels, and human-style "label next to the field" matching
15
+ * same locator conventions as the Appium helper, so most tests move between the two helpers unchanged
16
+ * interactive [pause](/basics#pause), auto-retries and other CodeceptJS features work out of the box
17
+
18
+ **Cons**:
19
+
20
+ * experimental (v0.1.x), the API may change
21
+ * no XPath or CSS locators — only accessibility-based ones
22
+ * text on screen ≠ text in the accessibility tree: some visible messages can't be asserted by text (common for React Native error labels)
23
+ * iOS + React Native: taps may not reach RN controls (known mobilecli issue); Android works
24
+
25
+ CodeceptJS allows you to try different options and choose the one which works best for you. If Mobilewright covers your app, tests run dramatically faster than with Appium.
26
+
27
+ ## Installation
28
+
29
+ Requirements: Node.js 22.12+, CodeceptJS 4, a booted simulator or emulator, or a connected device.
30
+
31
+ ```
32
+ npm i @codeceptjs/mobilewright --save-dev
33
+ ```
34
+
35
+ Check your environment with one command:
36
+
37
+ ```
38
+ npx mobilewright doctor
39
+ ```
40
+
41
+ It reports Node.js, mobilecli versions, connected devices and common misconfigurations. List available devices:
42
+
43
+ ```
44
+ npx mobilewright devices
45
+ ```
46
+
47
+ ## Configuration
48
+
49
+ ```js
50
+ exports.config = {
51
+ tests: './*_test.js',
52
+ output: './output',
53
+ helpers: {
54
+ Mobilewright: {
55
+ require: '@codeceptjs/mobilewright',
56
+ platform: 'android', // or 'ios'
57
+ app: 'com.saucelabs.mydemoapp.rn', // package name (Android) or bundle id (iOS)
58
+ installApps: './MyDemoApp.apk', // installed before tests
59
+ actionTimeout: 10000,
60
+ waitForTimeout: 10000,
61
+ animations: 'off', // disables system animations on device
62
+ },
63
+ },
64
+ plugins: {
65
+ screenshot: { enabled: true },
66
+ retryFailedStep: { enabled: true },
67
+ },
68
+ }
69
+ ```
70
+
71
+ | Option | Default | Description |
72
+ | --- | --- | --- |
73
+ | `platform` | required | `ios` or `android` |
74
+ | `app` | — | bundle id (iOS) or package name (Android) of the app under test |
75
+ | `installApps` | — | path or list of paths to `.apk`, `.app` or `.ipa` files installed before tests |
76
+ | `deviceId` | first booted device | UDID or serial of the device |
77
+ | `deviceName` | — | string or RegExp matched against device names |
78
+ | `url` | — | URL of a running mobilecli server; when set, the helper does not start one |
79
+ | `restart` | `'app'` | `'app'` relaunches the app before every test, `'session'` launches it once, `false` never launches it |
80
+ | `animations` | unchanged | `'off'` disables system animations |
81
+
82
+ The helper starts and stops a mobilecli server automatically; with `restart: 'app'` (default) the app is relaunched before every test, so no `Before()` hook is needed.
83
+
84
+ ## Locators
85
+
86
+ Mobilewright reads the accessibility tree. Locators follow the [Appium helper](/mobile) conventions:
87
+
88
+ | Locator | Finds |
89
+ | --- | --- |
90
+ | `'~login'` | test id, then accessibility label |
91
+ | `'#login'` | test id; on Android also matches `com.app:id/login` |
92
+ | `{ role: 'button', name: 'Sign In' }` | element by role and accessible name |
93
+ | `{ label }`, `{ text }`, `{ placeholder }`, `{ testId }` | the matching attribute; `text` accepts a RegExp |
94
+ | `{ android: '#a', ios: '~b' }` | a different locator per platform |
95
+
96
+ For `fillField` a plain name matches a text field by its accessibility label, its placeholder, or **the text label displayed next to the field** — the way a human reads a form. XPath and CSS are not supported and fail with an error suggesting a supported locator.
97
+
98
+ ## A Test
99
+
100
+ A test against the [Sauce Labs My Demo App](https://github.com/saucelabs/my-demo-app-rn) (React Native):
101
+
102
+ ```js
103
+ import { secret } from 'codeceptjs'
104
+
105
+ Feature('Sauce Labs Store')
106
+
107
+ Scenario('Open a product and add it to cart', ({ I }) => {
108
+ I.waitForText('Products', 30)
109
+ I.click('Sauce Labs Backpack') // click by visible text
110
+ I.waitForElement('~Add To Cart button', 15)
111
+ I.click('~Add To Cart button') // ~ = accessibility id / label
112
+ I.see('1', '~cart badge')
113
+ I.saveElementScreenshot('~cart badge', 'cart-badge.png') // element screenshot
114
+ })
115
+
116
+ Scenario('Deep link and fill fields by label', ({ I }) => {
117
+ I.waitForText('Products', 30)
118
+ I.amOnPage('mydemoapprn://login') // deep link
119
+ I.fillField('Username', 'bob@example.com') // found by the label next to the field
120
+ I.fillField('Password', secret('10203040')) // secret() keeps the password out of logs
121
+ I.click('~Login button')
122
+ I.waitForText('Products', 15)
123
+ })
124
+
125
+ Scenario('Rotate the screen', ({ I }) => {
126
+ I.setOrientation('LANDSCAPE')
127
+ I.seeOrientationIs('LANDSCAPE')
128
+ I.setOrientation('PORTRAIT')
129
+ })
130
+ ```
131
+
132
+ Run tests as usual:
133
+
134
+ ```
135
+ npx codeceptjs run --steps
136
+ ```
137
+
138
+ ## Debugging
139
+
140
+ * [Interactive pause](/basics#pause) works: drop `pause()` into a test and try steps and locators by hand.
141
+ * `DEBUG='mw:*' npx codeceptjs run` prints the whole device dialogue over WebSocket.
142
+ * `await I.grabSource()` returns the accessibility tree as JSON — the main tool for debugging locators: it shows every label, testId and element type on the current screen.
143
+ * `npx mobilewright screenshot -o screen.png` takes a device screenshot without any test.
144
+ * `npx mobilewright codegen` records taps on the device and generates a test; `npx mobilewright inspect` opens a live element inspector in the browser.
145
+
146
+ ## Appium vs Mobilewright
147
+
148
+ | | Appium | Mobilewright |
149
+ | --- | --- | --- |
150
+ | Server | Appium server + WebDriver required | none (adb / mobilecli direct) |
151
+ | Automation basis | UI dumps | accessibility tree |
152
+ | Speed | slow startup, minutes per suite | ~7-10s per test incl. app install |
153
+ | Locators | XPath, CSS, accessibility ids | accessibility ids, roles, labels, text |
154
+ | App instrumentation | none needed | none needed |
155
+ | Maturity | industry standard, huge ecosystem | experimental (v0.1.x) |
156
+ | Cloud (SauceLabs, BrowserStack) | yes | not yet |
157
+
158
+ Both helpers share locator conventions, so most tests move between them with little change — start with Mobilewright for fast local runs and keep an Appium job for cloud testing and XPath-heavy suites.
159
+
160
+ ## Known Pitfalls
161
+
162
+ 1. **Text on screen ≠ text in the accessibility tree.** Error messages may be visible on a screenshot but missing from the tree — text assertions on such elements fail. Check with `I.grabSource()` before asserting by text.
163
+ 2. **`clearField` on React Native fields is unstable**; the helper falls back to backspaces, but masked values (passwords) report the wrong length. Filling a password field twice is a flakiness candidate.
164
+ 3. **React Native components may not expose testIds** — on such apps `#id` locators don't match; `~` locators fall back to labels and usually work.
165
+ 4. **App state persists between tests**: `restart` relaunches the app but does not clear its data.
166
+ 5. **iOS + React Native**: taps may not reach RN controls (known mobilecli issue); Android works.
@@ -75,10 +75,10 @@ export default async function (selectedRuns, options) {
75
75
  if (options.config) {
76
76
  // update paths to config path
77
77
  if (config.tests) {
78
- config.tests = path.resolve(testRoot, config.tests)
78
+ config.tests = resolvePatterns(testRoot, config.tests)
79
79
  }
80
80
  if (config.gherkin && config.gherkin.features) {
81
- config.gherkin.features = path.resolve(testRoot, config.gherkin.features)
81
+ config.gherkin.features = resolvePatterns(testRoot, config.gherkin.features)
82
82
  }
83
83
  }
84
84
 
@@ -117,6 +117,11 @@ export default async function (selectedRuns, options) {
117
117
  })
118
118
  }
119
119
 
120
+ export function resolvePatterns(testRoot, patterns) {
121
+ const resolve = pattern => path.resolve(testRoot, pattern).split(path.sep).join(path.posix.sep)
122
+ return Array.isArray(patterns) ? patterns.map(resolve) : resolve(patterns)
123
+ }
124
+
120
125
  function executeRun(runName, runConfig) {
121
126
  // clone config
122
127
  let overriddenConfig = { ...config }
package/lib/container.js CHANGED
@@ -61,7 +61,7 @@ class Container {
61
61
  *
62
62
  */
63
63
  static get STANDARD_ACTING_HELPERS() {
64
- return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'Obscura', 'Lightpanda']
64
+ return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'Obscura', 'Lightpanda', 'Moli']
65
65
  }
66
66
  /**
67
67
  * Create container with all required helpers and support objects
@@ -244,7 +244,7 @@ class Decision extends Helper {
244
244
 
245
245
  _actingHelper() {
246
246
  const helper = pickActingHelper(Container.helpers())
247
- if (!helper) throw new Error('Decision helper requires a web helper: Playwright, Puppeteer, WebDriver, Obscura or Lightpanda')
247
+ if (!helper) throw new Error('Decision helper requires a web helper: Playwright, Puppeteer, WebDriver, Obscura, Lightpanda or Moli')
248
248
  return helper
249
249
  }
250
250
  }
@@ -0,0 +1,125 @@
1
+ import CDPBrowser from './CDPBrowser.js'
2
+
3
+ /**
4
+ * ## Configuration
5
+ *
6
+ * This helper should be configured in codecept.conf.js
7
+ *
8
+ * @typedef MoliConfig
9
+ * @type {object}
10
+ * @prop {string} [endpoint] - explicit CDP endpoint. Setting this switches the helper to ATTACH
11
+ * mode: it only connects, and never spawns or kills a process, no matter what else is configured.
12
+ * Leave it unset for SELF-MANAGED mode (see below).
13
+ * @prop {string} [binaryPath] - path to the `moli` executable, used in SELF-MANAGED mode
14
+ * (`endpoint` unset). Checked before `MOLI_PATH` and `PATH`.
15
+ * @prop {number} [port] - port `moli serve` listens on, in SELF-MANAGED mode. When unset, a free
16
+ * port is picked automatically, which is what makes `run-workers` collision-free — every worker
17
+ * gets its own instance on its own port with zero config.
18
+ * @prop {number} [serverStartTimeout=15000] - milliseconds to wait for a spawned `moli serve` to
19
+ * answer `/json/version` before `_connect` gives up.
20
+ */
21
+ const config = {}
22
+
23
+ /**
24
+ * Moli drives [Moli](https://github.com/lexmount/moli), an open-source headless browser written
25
+ * in Rust for automation, exposed over the Chrome DevTools Protocol. It runs real JavaScript in V8
26
+ * and computes styles with Servo's Stylo, but by default skips layout and painting: element boxes
27
+ * use deterministic placeholder geometry, so there are no screenshots and no coordinate input.
28
+ *
29
+ * This helper is a thin `CDPBrowser` subclass: it changes nothing about how locating or acting on
30
+ * elements works, it only pins the config presets Moli requires and manages the `moli serve`
31
+ * process lifecycle, the same way Playwright manages its own browser process.
32
+ *
33
+ * > Moli support is experimental. Pin the browser version in CI and keep a
34
+ * > Playwright/WebDriver job for browser-compatibility coverage.
35
+ *
36
+ * ## Modes
37
+ *
38
+ * - **ATTACH** — `endpoint` is set explicitly in the config. The helper only connects to it; it
39
+ * never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
40
+ * - **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
41
+ * the config, then the `MOLI_PATH` environment variable, then `moli` on `PATH`. The helper
42
+ * spawns `moli serve --host 127.0.0.1 --port <port>` (`port` from the config, or a free port
43
+ * picked automatically), waits for it to answer, connects, and kills it in `_finishTest`.
44
+ * - **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
45
+ * answers `http://127.0.0.1:9222/json/version` (e.g. `moli serve` started by hand). The helper
46
+ * attaches to it and never kills it. If neither a binary nor a running server on :9222 can be
47
+ * found, the helper throws a loud, actionable error.
48
+ *
49
+ * ## Install
50
+ *
51
+ * Follow the [official installation instructions](https://github.com/lexmount/moli#direct-installation),
52
+ * then put `moli` on your `PATH`, or point `binaryPath`/`MOLI_PATH` at it. The helper then
53
+ * launches and tears it down automatically.
54
+ *
55
+ * ## Config presets
56
+ *
57
+ * These are set automatically and only need overriding for unusual setups:
58
+ *
59
+ * | option | value | why |
60
+ * | --- | --- | --- |
61
+ * | `input` | `synthetic` | without layout Moli rejects coordinate mouse input, so `click` always takes the `forceClick` path |
62
+ * | `xpathPolyfill` | `auto` | probed per binary/page |
63
+ * | `capabilities.layout` | `real` | Moli computes real styles and gives displayed elements a non-empty placeholder box, which is enough for `seeElement` and the `waitForVisible` family; it is pinned because the runtime probe would also enable coordinate input |
64
+ * | `capabilities.screenshot` | `false` | screenshots require `moli serve --layout`, which this helper does not use: its layout is a snapshot that goes stale when the page changes |
65
+ *
66
+ * ## Limitations
67
+ *
68
+ * - No screenshots and no screencast: `saveScreenshot` and `saveElementScreenshot` do nothing (a debug message is printed).
69
+ * - No coordinate-based input (`clickXY`).
70
+ * - No frames or popups.
71
+ * - Uploaded files are sent as `application/octet-stream` regardless of their extension.
72
+ * - The default stylesheet does not color links: `a` computes `color: rgb(0, 0, 0)`.
73
+ *
74
+ * <!-- configuration -->
75
+ *
76
+ * ## Example
77
+ *
78
+ * ```js
79
+ * // inside codecept.conf.js — SELF-LAUNCH mode (recommended): the helper finds/starts/stops
80
+ * // moli serve on its own, on a free port. Ideal for run-workers: every worker gets its own
81
+ * // instance with no config.
82
+ * {
83
+ * helpers: {
84
+ * Moli: {
85
+ * url: 'http://localhost',
86
+ * }
87
+ * }
88
+ * }
89
+ * ```
90
+ *
91
+ * ```js
92
+ * // ATTACH mode — connect to a Moli instance you manage yourself (remote host, container, etc.)
93
+ * {
94
+ * helpers: {
95
+ * Moli: {
96
+ * url: 'http://localhost',
97
+ * endpoint: 'http://127.0.0.1:9222',
98
+ * }
99
+ * }
100
+ * }
101
+ * ```
102
+ *
103
+ * ## Methods
104
+ */
105
+ class Moli extends CDPBrowser {
106
+ /**
107
+ * @param {MoliConfig} config
108
+ */
109
+ constructor(config) {
110
+ super({
111
+ input: 'synthetic',
112
+ xpathPolyfill: 'auto',
113
+ ...config,
114
+ capabilities: { layout: 'real', screenshot: false, ...((config && config.capabilities) || {}) },
115
+ })
116
+ this._manageServer(config, {
117
+ name: 'moli',
118
+ envVar: 'MOLI_PATH',
119
+ args: port => ['serve', '--host', '127.0.0.1', '--port', String(port)],
120
+ releases: 'https://github.com/lexmount/moli/releases',
121
+ })
122
+ }
123
+ }
124
+
125
+ export default Moli
@@ -1079,7 +1079,6 @@ class WebDriver extends Helper {
1079
1079
  *
1080
1080
  */
1081
1081
  async click(locator, context = null) {
1082
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1083
1082
  const locateFn = prepareLocateFn.call(this, context)
1084
1083
 
1085
1084
  const res = await findClickable.call(this, locator, locateFn)
@@ -1090,7 +1089,7 @@ class WebDriver extends Helper {
1090
1089
  }
1091
1090
  const elem = selectElement(res, locator, this)
1092
1091
  highlightActiveElement.call(this, elem)
1093
- return this.browser[clickMethod](getElementId(elem))
1092
+ return this.browser.elementClick(getElementId(elem))
1094
1093
  }
1095
1094
 
1096
1095
  /**
@@ -1390,7 +1389,6 @@ class WebDriver extends Helper {
1390
1389
  * {{> checkOption }}
1391
1390
  */
1392
1391
  async checkOption(field, context = null) {
1393
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1394
1392
  const locateFn = prepareLocateFn.call(this, context)
1395
1393
 
1396
1394
  const res = await findCheckable.call(this, field, locateFn)
@@ -1403,7 +1401,7 @@ class WebDriver extends Helper {
1403
1401
  const isSelected = await isElementChecked(this.browser, elementId)
1404
1402
 
1405
1403
  if (isSelected) return Promise.resolve(true)
1406
- return this.browser[clickMethod](elementId)
1404
+ return this.browser.elementClick(elementId)
1407
1405
  }
1408
1406
 
1409
1407
  /**
@@ -1411,7 +1409,6 @@ class WebDriver extends Helper {
1411
1409
  * {{> uncheckOption }}
1412
1410
  */
1413
1411
  async uncheckOption(field, context = null) {
1414
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1415
1412
  const locateFn = prepareLocateFn.call(this, context)
1416
1413
 
1417
1414
  const res = await findCheckable.call(this, field, locateFn)
@@ -1424,7 +1421,7 @@ class WebDriver extends Helper {
1424
1421
  const isSelected = await isElementChecked(this.browser, elementId)
1425
1422
 
1426
1423
  if (!isSelected) return Promise.resolve(true)
1427
- return this.browser[clickMethod](elementId)
1424
+ return this.browser.elementClick(elementId)
1428
1425
  }
1429
1426
 
1430
1427
  /**
package/lib/mocha/cli.js CHANGED
@@ -1,5 +1,5 @@
1
- import mocha from 'mocha'
2
- const { reporters: { Base } } = mocha
1
+ import { reporters } from 'mocha'
2
+ const { Base } = reporters
3
3
  import ms from 'ms'
4
4
  import figures from 'figures'
5
5
  import { readFileSync } from 'fs'
@@ -1,5 +1,4 @@
1
- import Suite from 'mocha/lib/suite.js'
2
- import Test from 'mocha/lib/test.js'
1
+ import { Suite, Test } from 'mocha'
3
2
  import { BeforeHook, AfterHook, BeforeSuiteHook, AfterSuiteHook } from './hooks.js'
4
3
 
5
4
  export {
@@ -1,4 +1,4 @@
1
- import MochaSuite from 'mocha/lib/suite.js'
1
+ import { Suite as MochaSuite } from 'mocha'
2
2
  /**
3
3
  * @typedef {import('mocha')} Mocha
4
4
  */
package/lib/mocha/test.js CHANGED
@@ -1,5 +1,4 @@
1
- import Test from 'mocha/lib/test.js'
2
- import Suite from 'mocha/lib/suite.js'
1
+ import { Suite, Test } from 'mocha'
3
2
  import { genTestId, serializeError, clearString, relativeDir } from '../utils.js'
4
3
  import Step from '../step/base.js'
5
4
  import { enhanceMochaSuite } from './suite.js'
package/lib/mocha/ui.js CHANGED
@@ -7,7 +7,7 @@ import { createTest } from './test.js'
7
7
  import { createSuite } from './suite.js'
8
8
  import { HookConfig, AfterSuiteHook, AfterHook, BeforeSuiteHook, BeforeHook } from './hooks.js'
9
9
  import { initMochaGlobals } from '../globals.js'
10
- import common from 'mocha/lib/interfaces/common.js'
10
+ import { createCommon } from 'mocha/lib/interfaces/common.js'
11
11
  import container from '../container.js'
12
12
  import store from '../store.js'
13
13
 
@@ -49,7 +49,7 @@ export default function (suite) {
49
49
  let afterEachHooksAreLoaded
50
50
 
51
51
  suite.on('pre-require', (context, file, mocha) => {
52
- const cmn = common(suites, context, mocha)
52
+ const cmn = createCommon(suites, context, mocha)
53
53
 
54
54
  const addScenario = function (title, opts = {}, fn) {
55
55
  const suite = suites[0]
@@ -74,7 +74,7 @@ export default function (suite) {
74
74
  context.BeforeAll = cmn.before
75
75
  context.AfterAll = cmn.after
76
76
 
77
- context.run = mocha.options.delay && common.runWithSuite(suite)
77
+ context.run = mocha.options.delay && cmn.runWithSuite(suite)
78
78
  /**
79
79
  * Describe a "suite" with the given `title`
80
80
  * and callback `fn` containing nested suites
@@ -109,7 +109,7 @@ function initUrlMode(pattern) {
109
109
  const helper = getBrowserHelper()
110
110
 
111
111
  if (!helper) {
112
- output.error('pause:on=url requires a browser helper (Playwright, WebDriver, Puppeteer, Appium, Obscura, Lightpanda)')
112
+ output.error('pause:on=url requires a browser helper (Playwright, WebDriver, Puppeteer, Appium, Obscura, Lightpanda, Moli)')
113
113
  return
114
114
  }
115
115