@maeris/maeris-player 0.1.0 → 0.1.1

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.
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Maeris Player
2
2
 
3
- Local Playwright-based runner for Maeris "Run in Browser".
3
+ Local Playwright-based runner for Maeris **Run in Browser**.
4
+ Start the runner once, then trigger runs from the Maeris web app.
5
+
6
+ ## Prerequisites
7
+ - Node.js 18+
8
+ - macOS / Windows / Linux
9
+ - Access to the `@maeris` npm scope (this package may be private)
4
10
 
5
11
  ## Goals
6
12
  - Replay recorder steps through the Chrome extension in a local browser window.
@@ -13,31 +19,77 @@ Local Playwright-based runner for Maeris "Run in Browser".
13
19
  3. **Playwright adapter (`src/runner/playwrightAdapter.js`)** performs clicks, inputs, navigations, assertions; captures screenshots on failure.
14
20
  4. **Event contract (`src/runner/events.js`)** defines the message names (`STEP_STARTED`, `STEP_PASSED`, `STEP_FAILED`, `NAVIGATION`, `REPLAY_COMPLETE`, etc.).
15
21
 
16
- ## Running locally
22
+ ## Quick start
17
23
  ```bash
18
- npm install
24
+ npx @maeris/maeris-player
25
+ ```
19
26
 
20
- # listen for Run in Browser traffic from the web app (headed by default)
21
- npm start
27
+ This starts the local runner on `ws://localhost:8090` (headed by default).
28
+ Keep it running, then click **Run in Browser** in the Maeris UI.
22
29
 
23
- # replay a recorded JSON file
24
- npm start -- --steps path/to/recorded.json --url https://demo-app/ --browser chromium
30
+ If you prefer a global install:
31
+ ```bash
32
+ npm i -g @maeris/maeris-player
33
+ maeris-player
25
34
  ```
26
35
 
27
- ## Usage via npm
36
+ ## How users run tests
37
+ 1. Start the runner:
38
+ ```bash
39
+ npx @maeris/maeris-player
40
+ ```
41
+ 2. Open the Maeris app and click **Run in Browser**.
42
+ 3. The runner opens a local browser window and executes the steps.
43
+
44
+ ## Production usage options
45
+ ### Option A — Extension proxy (recommended)
46
+ Works from `https://` without TLS.
47
+ The Maeris extension connects to the local runner and proxies events.
48
+
49
+ 1. Install the Maeris Chrome extension.
50
+ 2. Start the runner:
51
+ ```bash
52
+ npx @maeris/maeris-player
53
+ ```
54
+ 3. Click **Run in Browser** in the Maeris app.
55
+
56
+ ### Option B — TLS direct (no extension)
57
+ Connect directly from an `https://` web app using `wss://localhost`.
58
+
28
59
  ```bash
60
+ npx @maeris/maeris-player setup-cert
61
+
62
+ # macOS/Linux
63
+ MAERIS_TLS_CERT="$HOME/.maeris-player/certs/localhost.cert.pem" \
64
+ MAERIS_TLS_KEY="$HOME/.maeris-player/certs/localhost.key.pem" \
29
65
  npx @maeris/maeris-player
30
66
  ```
31
67
 
32
- This starts the local runner on `ws://localhost:8090`. Keep it running, then click **Run in Browser** in the Maeris UI.
33
-
34
- ## HTTPS / WSS support (for prod without extension)
35
- If you want to connect from an `https://` web app directly to the runner, start it with TLS:
68
+ Then trust the certificate as instructed by `setup-cert`.
69
+ The runner will listen on `wss://localhost:8090`.
36
70
 
71
+ Tip: if you used the default cert location, you can also run:
37
72
  ```bash
38
- npx @maeris/maeris-player setup-cert
39
- MAERIS_TLS_CERT=/path/to/cert.pem MAERIS_TLS_KEY=/path/to/key.pem npx @maeris/maeris-player
73
+ maeris-player --tls
40
74
  ```
41
75
 
42
- The runner will listen on `wss://localhost:8090`. You will need to trust the certificate in your OS/browser.
43
- # maeris-player
76
+ ## Troubleshooting
77
+ - If the Maeris web app shows "Extension required" on an `https://` page:
78
+ - use the Maeris Player (TLS) option in the modal, or
79
+ - install the extension (Option A).
80
+ - If runs don't start, confirm the runner is listening:
81
+ - `ws://localhost:8090` for non-TLS
82
+ - `wss://localhost:8090` for TLS
83
+ - If publish fails with 2FA errors, see "Publishing" below.
84
+
85
+ ## Publishing
86
+ This is published as a public scoped package (`@maeris/maeris-player`).
87
+
88
+ If your npm org enforces 2FA, you will need either:
89
+ - an OTP at publish time (`npm publish --otp=123456`), or
90
+ - a granular access token with "bypass 2FA for publish" enabled.
91
+
92
+ First publish (or if npm requires it for scoped packages):
93
+ ```bash
94
+ npm publish --access public
95
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maeris/maeris-player",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "description": "Local Playwright-based runner for Maeris",
6
6
  "scripts": {
@@ -12,6 +12,9 @@
12
12
  "bin": {
13
13
  "maeris-player": "src/cli/run.js"
14
14
  },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
15
18
  "license": "MIT",
16
19
  "dependencies": {
17
20
  "playwright": "^1.44.0",
package/src/cli/run.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
+ const os = require('os');
4
5
  const yargs = require('yargs/yargs');
5
6
  const { hideBin } = require('yargs/helpers');
6
7
  const { Runner } = require('../runner');
@@ -38,6 +39,12 @@ const argv = yargs(hideBin(process.argv))
38
39
  default: true,
39
40
  describe: 'Start the runner server and wait for UI commands',
40
41
  })
42
+ .option('tls', {
43
+ type: 'boolean',
44
+ default: false,
45
+ describe:
46
+ 'Start in TLS mode for HTTPS pages (uses ~/.maeris-player/certs unless MAERIS_TLS_* env vars are set)',
47
+ })
41
48
  .option('cert-dir', {
42
49
  type: 'string',
43
50
  describe: 'Directory to store TLS certs for setup-cert',
@@ -45,6 +52,21 @@ const argv = yargs(hideBin(process.argv))
45
52
  .help()
46
53
  .parse();
47
54
 
55
+ function applyTlsDefaults() {
56
+ if (process.env.MAERIS_TLS_CERT && process.env.MAERIS_TLS_KEY) return;
57
+ const baseDir = path.join(os.homedir(), '.maeris-player', 'certs');
58
+ const certPath = path.join(baseDir, 'localhost.cert.pem');
59
+ const keyPath = path.join(baseDir, 'localhost.key.pem');
60
+ if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
61
+ process.env.MAERIS_TLS_CERT = certPath;
62
+ process.env.MAERIS_TLS_KEY = keyPath;
63
+ return;
64
+ }
65
+ throw new Error(
66
+ `TLS cert not found. Run \"maeris-player setup-cert\" first (expected ${certPath} and ${keyPath}).`
67
+ );
68
+ }
69
+
48
70
  function loadSteps(filePath) {
49
71
  const resolved = path.resolve(filePath);
50
72
  if (!fs.existsSync(resolved)) {
@@ -65,6 +87,10 @@ function loadSteps(filePath) {
65
87
  return;
66
88
  }
67
89
 
90
+ if (argv.tls) {
91
+ applyTlsDefaults();
92
+ }
93
+
68
94
  const steps = argv.steps ? loadSteps(argv.steps) : [];
69
95
  const options = {
70
96
  steps,
@@ -73,6 +73,8 @@ function printInstructions({ certPath, keyPath }) {
73
73
  console.log(` ${linux}`);
74
74
  console.log('\nThen start the runner with:');
75
75
  console.log(` MAERIS_TLS_CERT="${certPath}" MAERIS_TLS_KEY="${keyPath}" npx @maeris/maeris-player\n`);
76
+ console.log('Or (if you installed globally):');
77
+ console.log(' maeris-player --tls\n');
76
78
  }
77
79
 
78
80
  module.exports = {
@@ -42,7 +42,7 @@ class PlaywrightAdapter extends EventEmitter {
42
42
  await this.performStep(this.page, step);
43
43
  this.emit('event', createStepPayload(EVENTS.STEP_PASSED, step));
44
44
  } catch (error) {
45
- const screenshot = await page.screenshot({ fullPage: true });
45
+ const screenshot = await this.page.screenshot({ fullPage: true });
46
46
  this.emit(
47
47
  'event',
48
48
  createStepPayload(EVENTS.STEP_FAILED, step, {
@@ -119,15 +119,23 @@ class PlaywrightAdapter extends EventEmitter {
119
119
  }
120
120
 
121
121
  applyNth(locator, step) {
122
+ const parseNth = (value) => {
123
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
124
+ if (typeof value === 'string' && value.trim() !== '') {
125
+ const parsed = Number.parseInt(value, 10);
126
+ if (Number.isFinite(parsed)) return parsed;
127
+ }
128
+ return null;
129
+ };
130
+
122
131
  const nth =
123
- typeof step.nth_appearance === 'number'
124
- ? step.nth_appearance
125
- : typeof step.nthAppearance === 'number'
126
- ? step.nthAppearance
127
- : typeof step.step_index === 'number'
128
- ? step.step_index
129
- : null;
130
- return nth !== null ? locator.nth(nth) : locator;
132
+ parseNth(step.nth_appearance) ??
133
+ parseNth(step.nthAppearance) ??
134
+ parseNth(step.step_index);
135
+
136
+ // If nth is missing, use first() to avoid strict-mode violations on broad selectors.
137
+ if (nth === null || nth < 0) return locator.first();
138
+ return locator.nth(nth);
131
139
  }
132
140
 
133
141
  async performStep(page, step) {