@osfactory/har 1.15.0 → 1.16.0

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/dist/index.js CHANGED
@@ -40502,12 +40502,11 @@ function listPm2Processes() {
40502
40502
  return void 0;
40503
40503
  }
40504
40504
  }
40505
- function detectForeignPm2(projectName, agentId, procs) {
40505
+ function detectForeignPm2(_projectName, agentId, procs) {
40506
40506
  if (!procs) return void 0;
40507
- const slotPrefix = `har-${projectName}-agent-${agentId}-`;
40508
40507
  const legacyPrefix = `agent-${agentId}-`;
40509
40508
  const foreign = procs.filter(
40510
- (p) => p.name && (p.name.startsWith("har-") && p.name.includes(`-agent-${agentId}-`) && !p.name.startsWith(slotPrefix) || p.name.startsWith(legacyPrefix) && !p.name.startsWith("har-"))
40509
+ (p) => p.name && p.name.startsWith(legacyPrefix) && !p.name.startsWith("har-")
40511
40510
  );
40512
40511
  if (foreign.length === 0) return void 0;
40513
40512
  return {
@@ -40517,6 +40516,48 @@ function detectForeignPm2(projectName, agentId, procs) {
40517
40516
  }))
40518
40517
  };
40519
40518
  }
40519
+ function freeAgentSlots(harnessRoot, projectName, agentId, procs) {
40520
+ let ids = [];
40521
+ let range;
40522
+ try {
40523
+ range = getAgentSlotRange(harnessRoot);
40524
+ ids = getAgentSlotIds(harnessRoot);
40525
+ } catch {
40526
+ return { free: [] };
40527
+ }
40528
+ const free = ids.filter((id) => {
40529
+ if (id === agentId) return false;
40530
+ const session = readSlotRegistry(harnessRoot, id);
40531
+ if (session !== void 0 && session.status !== "completed") return false;
40532
+ return detectForeignPm2(projectName, id, procs) === void 0;
40533
+ });
40534
+ return { free, range };
40535
+ }
40536
+ function foreignPm2Advice(agentId, processes, free, range) {
40537
+ const cwds = [
40538
+ ...new Set(processes.map((p) => p.cwd).filter((cwd) => Boolean(cwd)))
40539
+ ];
40540
+ const rangeLabel = range ? `agentSlots ${range.min}\u2013${range.max}` : "agentSlots in .har/stages.json";
40541
+ const higher = free.filter((id) => id > agentId);
40542
+ const suggested = higher[0] ?? free[0];
40543
+ const lines = [];
40544
+ if (suggested !== void 0) {
40545
+ const kind = higher.length > 0 ? "higher slot" : "slot";
40546
+ lines.push(
40547
+ `Use a free ${kind}: har env launch ${suggested} (free: ${free.join(", ")}; ${rangeLabel}).`
40548
+ );
40549
+ } else if (range) {
40550
+ lines.push(`No free slot in ${rangeLabel}. Raise agentSlots.max in .har/stages.json.`);
40551
+ } else {
40552
+ lines.push("Use a different agent slot (see agentSlots in .har/stages.json).");
40553
+ }
40554
+ lines.push(
40555
+ "These processes belong to another project and must not be deleted from this checkout."
40556
+ );
40557
+ const where = cwds.length === 0 ? "that project checkout (cwd unknown)" : cwds.join(", ");
40558
+ lines.push(`To stop that other session, run \`har env teardown ${agentId}\` in ${where}.`);
40559
+ return { lines, suggested };
40560
+ }
40520
40561
  function infraEnabled2(env3, service) {
40521
40562
  return ` ${env3.HARNESS_INFRA_SERVICES ?? ""} `.includes(` ${service} `);
40522
40563
  }
@@ -40570,12 +40611,17 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
40570
40611
  const foreign = usesPm2 ? detectForeignPm2(projectName, agentId, pm2Procs) : void 0;
40571
40612
  if (foreign) {
40572
40613
  const names = foreign.processes.map((p) => p.name).join(", ");
40614
+ const slots = freeAgentSlots(harnessRoot, projectName, agentId, pm2Procs);
40615
+ const advice = foreignPm2Advice(agentId, foreign.processes, slots.free, slots.range);
40573
40616
  blockers.push({
40574
40617
  code: "foreign_pm2",
40575
40618
  message: `Foreign PM2 processes match agent ${agentId}: ${names}`,
40576
- remediation: "Stop the other harness session (`har env teardown` in that repo) or use a different agent slot.",
40577
- details: { processes: foreign.processes }
40619
+ remediation: advice.lines.join(" "),
40620
+ details: { processes: foreign.processes, adviceLines: advice.lines }
40578
40621
  });
40622
+ if (advice.suggested !== void 0) {
40623
+ remediations.push(`har env launch ${advice.suggested}`);
40624
+ }
40579
40625
  remediations.push(`Inspect with: ${packageRunner()} pm2 jlist | grep agent-${agentId}`);
40580
40626
  }
40581
40627
  if (usesPm2 && pm2Procs) {
@@ -40728,10 +40774,14 @@ function bashBlockerLines(agentId, b2) {
40728
40774
  switch (b2.code) {
40729
40775
  case "foreign_pm2": {
40730
40776
  const procs = details.processes ?? [];
40777
+ const advice = Array.isArray(details.adviceLines) ? details.adviceLines : [
40778
+ "Use a different agent slot.",
40779
+ "These processes belong to another project and must not be deleted from this checkout."
40780
+ ];
40731
40781
  return [
40732
40782
  `ERROR: foreign PM2 processes match agent ${agentId}:`,
40733
40783
  ...procs.map((p) => ` ${p.name} cwd=${p.cwd ?? "unknown"}`),
40734
- " Stop the other harness session or use a different slot."
40784
+ ...advice.map((line) => ` ${line}`)
40735
40785
  ];
40736
40786
  }
40737
40787
  case "control_port_conflict":
@@ -40834,6 +40884,7 @@ var init_slot_preflight = __esm({
40834
40884
  init_slot_launch_guard_occupied();
40835
40885
  init_slot_ports();
40836
40886
  init_capabilities();
40887
+ init_stages();
40837
40888
  init_slot_registry();
40838
40889
  init_worktree_untracked();
40839
40890
  import_child_process11 = require("child_process");
@@ -42500,7 +42551,7 @@ function detectPm2Issue(projectName, agentId, session, procs) {
42500
42551
  const legacyPrefix = `agent-${agentId}-`;
42501
42552
  const owned = procs.filter((p) => p.name?.startsWith(slotPrefix));
42502
42553
  const foreign = procs.filter(
42503
- (p) => p.name && (p.name.startsWith("har-") && p.name.includes(`-agent-${agentId}-`) && !p.name.startsWith(slotPrefix) || p.name.startsWith(legacyPrefix) && !p.name.startsWith("har-"))
42554
+ (p) => p.name && p.name.startsWith(legacyPrefix) && !p.name.startsWith("har-")
42504
42555
  );
42505
42556
  if (foreign.length > 0) return "foreign_pm2";
42506
42557
  if (owned.length > 0) {
@@ -0,0 +1,25 @@
1
+ # Cypress end-to-end tests — official GitHub Action
2
+ # https://github.com/cypress-io/github-action
3
+ #
4
+ # Adapt `start` and `wait-on` to this repo's dev server. The action installs
5
+ # dependencies, downloads the Cypress binary, and runs `cypress run`.
6
+ name: Cypress tests
7
+
8
+ on:
9
+ push:
10
+ pull_request:
11
+
12
+ jobs:
13
+ cypress-run:
14
+ runs-on: ubuntu-24.04
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+ - name: Cypress run
19
+ uses: cypress-io/github-action@v7
20
+ with:
21
+ start: npm run dev
22
+ wait-on: http://localhost:3000
23
+ browser: chrome
24
+ env:
25
+ CYPRESS_BASE_URL: http://localhost:3000
@@ -0,0 +1,81 @@
1
+ # Cypress (cypress-e2e)
2
+
3
+ Adapt specs under `cypress/e2e/` for your application. Full verification (`verify --full`) runs this stage when it is listed in `verificationStages` (the plugin adds it).
4
+
5
+ | Path | Purpose |
6
+ |------|---------|
7
+ | `cypress/e2e/` | End-to-end specs — extend when you add UI |
8
+ | `cypress/support/e2e.js` | Shared commands loaded before every spec |
9
+ | `cypress.config.js` | Base URL, spec pattern, artifact folders |
10
+
11
+ ## Run
12
+
13
+ After `har env launch <id>` and `npm install`:
14
+
15
+ ```bash
16
+ npx cypress verify
17
+ ./.har/stages/cypress-e2e.sh <id>
18
+ # included in:
19
+ har env verify <id> --full
20
+ ```
21
+
22
+ `cypress run` is headless. Cypress 16 deprecates the bundled Electron browser, so the stage uses Chrome when `google-chrome` is on PATH (or Google Chrome.app on macOS), then Chromium, then Electron. Set `HARNESS_CYPRESS_BROWSER` (`chrome`, `chromium`, `firefox`, `electron`) to override. Electron still runs in Cypress 16 and prints a deprecation warning.
23
+
24
+ ## Ports
25
+
26
+ The stage reads the slot env file and points Cypress at that slot:
27
+
28
+ | Variable | Source | Cypress |
29
+ |----------|--------|---------|
30
+ | Frontend | `FE_PORT` (or `HARNESS_FE_BASE_PORT + id * 10`) | `CYPRESS_BASE_URL` → `cy.visit('/')` |
31
+ | API | `API_PORT` (or `HARNESS_API_BASE_PORT + id * 10`) | `CYPRESS_API_URL` → `Cypress.expose('API_URL')` |
32
+ | Health path | `HARNESS_HEALTH_CHECK_PATH` (default `/health`) | `Cypress.expose('HEALTH_PATH')` |
33
+
34
+ Do not hardcode slot ports in specs. A monolith that serves UI and API on one port should set both bases to that origin in `.har/harness.env` (or only assert against `baseUrl`).
35
+
36
+ The scaffold `api.cy.js` expects `GET /health` to return 200. Change the path, or set `HARNESS_HEALTH_CHECK_PATH`, to match your app.
37
+
38
+ ## Artifacts
39
+
40
+ Written under the main repo's `.har/artifacts/cypress-e2e/` (not the worktree):
41
+
42
+ | Path | Contents |
43
+ |------|----------|
44
+ | `cypress.log` | Full `cypress run` output |
45
+ | `screenshots/` | Automatic failure screenshots |
46
+ | `videos/` | Present when you set `video: true` in `cypress.config.js` |
47
+
48
+ Video recording is off by default (`video: false`), matching current Cypress. Turn it on in config when you want a video per spec.
49
+
50
+ Exit codes use `--posix-exit-codes`: `0` when every spec passes, `1` when Cypress fails. The stage propagates that code. Without the flag, Cypress exits with the number of failed tests.
51
+
52
+ ## Linux libraries
53
+
54
+ `npx cypress verify` checks that the Electron binary can start. If it reports missing libraries, install the packages listed in the [Cypress Linux prerequisites](https://docs.cypress.io/app/get-started/install-cypress#Linux-Prerequisites) and verify again.
55
+
56
+ ## Existing Cypress projects
57
+
58
+ `add-plugin` will not overwrite a `cypress.config.js` you already have. Point `specPattern` at your specs, keep artifact folders under `.har/artifacts/cypress-e2e/`, and let the stage export `CYPRESS_BASE_URL` rather than a fixed `baseUrl`.
59
+
60
+ ## New UI features
61
+
62
+ Add or update a spec so `cypress-e2e` covers the change. Prefer one file per feature under `cypress/e2e/<feature>.cy.js`. Full verification must pass before done.
63
+
64
+ ## CI
65
+
66
+ `har env add-plugin cypress --with-ci` copies `.github/workflows/cypress.yml`, the official `cypress-io/github-action@v7` recipe. Change `start` and `wait-on` to this repo's dev command and URL.
67
+
68
+ ## Plugin updates
69
+
70
+ When HAR ships a new plugin template version, merge drift from:
71
+
72
+ ```bash
73
+ har env maintain
74
+ # review .har/maintain/plugins/cypress/
75
+ ```
76
+
77
+ Or refresh all plugin-owned files:
78
+
79
+ ```bash
80
+ har env add-plugin cypress --force
81
+ ```
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ # Cypress end-to-end tests for an agent slot.
3
+ # Outputs JSON to stdout, human-readable progress to stderr.
4
+ #
5
+ # Usage: ./.har/stages/cypress-e2e.sh <agent-id>
6
+ # Prerequisite: har env launch <agent-id> AND npm install (Cypress binary)
7
+ # See: ./.har/stages/CYPRESS.md
8
+ set -euo pipefail
9
+
10
+ # 1.0 stage surface: the runner exports WORK_DIR, ENV_FILE, AGENT_ID and
11
+ # HAR_HARNESS_DIR, with harness.env and the slot env file already sourced —
12
+ # agent-slot.sh is retired (1.0 migration).
13
+ HARNESS_DIR="${HAR_HARNESS_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
14
+
15
+ AGENT_ID="${1:-${AGENT_ID:?Usage: cypress-e2e.sh <agent-id>}}"
16
+
17
+ # Direct invocation (the manifest nextSteps) has no runner in front of it.
18
+ # Recover the slot work dir from the registry launch wrote.
19
+ if [ -z "${ENV_FILE:-}" ] || [ -z "${WORK_DIR:-}" ]; then
20
+ SLOT_FILE="$HARNESS_DIR/slots/agent-${AGENT_ID}.json"
21
+ if [ -f "$SLOT_FILE" ]; then
22
+ SLOT_WORK="$(node -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(s.workDir||"")' "$SLOT_FILE" 2>/dev/null || true)"
23
+ if [ -n "$SLOT_WORK" ] && [ -f "$SLOT_WORK/.env.agent.${AGENT_ID}" ]; then
24
+ WORK_DIR="$SLOT_WORK"
25
+ ENV_FILE="$SLOT_WORK/.env.agent.${AGENT_ID}"
26
+ set -a
27
+ # shellcheck disable=SC1091
28
+ [ -f "$HARNESS_DIR/harness.env" ] && . "$HARNESS_DIR/harness.env"
29
+ # shellcheck disable=SC1090
30
+ . "$ENV_FILE"
31
+ set +a
32
+ fi
33
+ fi
34
+ fi
35
+
36
+ now_ms() { node -e 'process.stdout.write(String(Date.now()))' 2>/dev/null || echo 0; }
37
+
38
+ log() { echo "==> [cypress-e2e agent-$AGENT_ID] $*" >&2; }
39
+
40
+ ENV_FILE="${ENV_FILE:?No slot env for agent ${AGENT_ID} — run har env launch ${AGENT_ID} first}"
41
+ WORK_DIR="${WORK_DIR:?No slot work dir for agent ${AGENT_ID} — run har env launch ${AGENT_ID} first}"
42
+
43
+ FE_PORT="${FE_PORT:-$(( ${HARNESS_FE_BASE_PORT:-3000} + AGENT_ID * 10 ))}"
44
+ API_PORT="${API_PORT:-$(( ${HARNESS_API_BASE_PORT:-8000} + AGENT_ID * 10 ))}"
45
+
46
+ export BASE_URL="${BASE_URL:-http://localhost:${FE_PORT}}"
47
+ export API_URL="${API_URL:-http://localhost:${API_PORT}}"
48
+ export CYPRESS_BASE_URL="${CYPRESS_BASE_URL:-$BASE_URL}"
49
+ export CYPRESS_API_URL="${CYPRESS_API_URL:-$API_URL}"
50
+ HEALTH_PATH="${HARNESS_HEALTH_CHECK_PATH:-${HARNESS_HEALTH_PATH:-/health}}"
51
+ export CYPRESS_HEALTH_PATH="${CYPRESS_HEALTH_PATH:-$HEALTH_PATH}"
52
+
53
+ # Artifacts land next to this harness (main checkout), not inside a worktree
54
+ # whose .env.agent file may rewrite REPO_ROOT.
55
+ ARTIFACT_DIR="$(cd "$HARNESS_DIR/.." && pwd)/.har/artifacts/cypress-e2e"
56
+ mkdir -p "$ARTIFACT_DIR"
57
+ export HARNESS_CYPRESS_ARTIFACT_DIR="$ARTIFACT_DIR"
58
+
59
+ # Cypress 16 deprecates the bundled Electron browser. Prefer an installed Chrome
60
+ # or Chromium; fall back to Electron so `npm install` is still enough to run.
61
+ if [ -n "${HARNESS_CYPRESS_BROWSER:-}" ]; then
62
+ BROWSER="$HARNESS_CYPRESS_BROWSER"
63
+ elif command -v google-chrome >/dev/null 2>&1 || command -v google-chrome-stable >/dev/null 2>&1 || [ -d "/Applications/Google Chrome.app" ]; then
64
+ BROWSER="chrome"
65
+ elif command -v chromium >/dev/null 2>&1 || command -v chromium-browser >/dev/null 2>&1; then
66
+ BROWSER="chromium"
67
+ else
68
+ BROWSER="electron"
69
+ fi
70
+ export HARNESS_CYPRESS_BROWSER="$BROWSER"
71
+
72
+ if [ ! -x "$WORK_DIR/node_modules/.bin/cypress" ]; then
73
+ echo "Error: Cypress is not installed in $WORK_DIR." >&2
74
+ echo " npm install" >&2
75
+ echo " npx cypress verify" >&2
76
+ echo " Then re-run: ./.har/stages/cypress-e2e.sh ${AGENT_ID}" >&2
77
+ exit 1
78
+ fi
79
+
80
+ log "Running Cypress ($BROWSER) against $CYPRESS_BASE_URL (API: $CYPRESS_API_URL)"
81
+ log "Work dir: $WORK_DIR"
82
+ log "Artifacts: $ARTIFACT_DIR"
83
+
84
+ START_TOTAL=$(now_ms)
85
+ LOG_FILE="$ARTIFACT_DIR/cypress.log"
86
+
87
+ set +e
88
+ (
89
+ cd "$WORK_DIR"
90
+ npx cypress run \
91
+ --e2e \
92
+ --browser "$BROWSER" \
93
+ --posix-exit-codes
94
+ ) >"$LOG_FILE" 2>&1
95
+ CYPRESS_EXIT=$?
96
+ set -e
97
+
98
+ END_TOTAL=$(now_ms)
99
+ TOTAL_MS=$(( END_TOTAL - START_TOTAL ))
100
+
101
+ if [ "$CYPRESS_EXIT" -eq 0 ]; then
102
+ log "Cypress passed (${TOTAL_MS}ms)"
103
+ else
104
+ log "Cypress failed with exit $CYPRESS_EXIT (${TOTAL_MS}ms) — see $LOG_FILE"
105
+ tail -30 "$LOG_FILE" | sed 's/^/ /' >&2
106
+ fi
107
+
108
+ HARNESS_CYPRESS_ARTIFACT_DIR="$ARTIFACT_DIR" node -e "
109
+ const fs = require('fs');
110
+ const artifactDir = process.env.HARNESS_CYPRESS_ARTIFACT_DIR;
111
+ const out = {
112
+ status: ${CYPRESS_EXIT} === 0 ? 'pass' : 'fail',
113
+ stageId: 'cypress-e2e',
114
+ kind: 'test',
115
+ agent_id: ${AGENT_ID},
116
+ total_ms: ${TOTAL_MS},
117
+ exit_code: ${CYPRESS_EXIT},
118
+ browser: process.env.HARNESS_CYPRESS_BROWSER || 'electron',
119
+ urls: [
120
+ { label: 'frontend', url: process.env.CYPRESS_BASE_URL || '' },
121
+ { label: 'api', url: process.env.CYPRESS_API_URL || '' },
122
+ ],
123
+ artifacts: [
124
+ { path: '.har/artifacts/cypress-e2e', kind: 'directory' },
125
+ { path: '.har/artifacts/cypress-e2e/cypress.log', kind: 'log' },
126
+ ],
127
+ };
128
+ if (fs.existsSync(artifactDir + '/screenshots')) {
129
+ out.artifacts.push({ path: '.har/artifacts/cypress-e2e/screenshots', kind: 'directory' });
130
+ }
131
+ process.stdout.write(JSON.stringify(out, null, 2) + '\n');
132
+ "
133
+
134
+ exit "$CYPRESS_EXIT"
@@ -0,0 +1,14 @@
1
+ # Cypress HAR plugin
2
+
3
+ This plugin was added by `har env add-plugin cypress`. It registers a `cypress-e2e` stage and scaffolds a minimal Cypress end-to-end suite.
4
+
5
+ ## Next steps
6
+
7
+ ```bash
8
+ npm install
9
+ npx cypress verify
10
+ har env launch 1
11
+ ./.har/stages/cypress-e2e.sh 1
12
+ ```
13
+
14
+ See `.har/stages/CYPRESS.md` for adaptation checklist, ports, and artifact layout.
@@ -0,0 +1,13 @@
1
+ // TODO: adapt the path for your app's health endpoint.
2
+ // Public config comes from cypress.config.js `expose` (Cypress.env was removed in Cypress 16).
3
+ describe('api health', () => {
4
+ it('health endpoint responds', () => {
5
+ const healthPath = Cypress.expose('HEALTH_PATH') || '/health';
6
+ const apiUrl = String(Cypress.expose('API_URL') || Cypress.config('baseUrl') || '').replace(
7
+ /\/$/,
8
+ '',
9
+ );
10
+ const requestPath = String(healthPath).startsWith('/') ? String(healthPath) : `/${healthPath}`;
11
+ cy.request(`${apiUrl}${requestPath}`).its('status').should('eq', 200);
12
+ });
13
+ });
@@ -0,0 +1,7 @@
1
+ // TODO: adapt routes and selectors for your app.
2
+ describe('smoke', () => {
3
+ it('homepage loads', () => {
4
+ cy.visit('/');
5
+ cy.get('body').should('be.visible');
6
+ });
7
+ });
@@ -0,0 +1 @@
1
+ // Loaded before every spec. Add app-wide commands here.
@@ -0,0 +1,68 @@
1
+ // Cypress config for the HAR `cypress-e2e` verification stage.
2
+ //
3
+ // Harness integration
4
+ // -------------------
5
+ // - Stage id: `cypress-e2e` (`.har/stages/cypress-e2e.sh`, registered in `stages.json`).
6
+ // - Runs on FULL verify only — not on quick verify:
7
+ // har env verify <id> --full
8
+ // - Quick verify stops at the quick-tier stages (typecheck, unit tests, api-health).
9
+ //
10
+ // Prerequisites
11
+ // -------------
12
+ // 1. Launch a slot first: har env launch <id>
13
+ // 2. Install Cypress: npm install && npx cypress verify
14
+ // The Cypress npm package downloads the Electron binary. On Linux, system
15
+ // libraries from the Cypress docs may also be required.
16
+ //
17
+ // Environment (injected by cypress-e2e.sh — never hardcode slot ports in specs)
18
+ // ------------------------------------------------------------------------------
19
+ // CYPRESS_BASE_URL Frontend origin for cy.visit('/') (slot FE port)
20
+ // CYPRESS_API_URL API origin (Cypress.expose('API_URL'); Cypress.env was removed in Cypress 16)
21
+ // CYPRESS_HEALTH_PATH Health path (Cypress.expose('HEALTH_PATH'), default /health)
22
+ // HARNESS_CYPRESS_ARTIFACT_DIR Absolute artifact directory for this run
23
+ // HARNESS_CYPRESS_BROWSER Browser passed to `cypress run` (chrome when installed, else electron)
24
+ //
25
+ // Test layout — agents must add or update specs for every UI change
26
+ // ------------------------------------------------------------------
27
+ // cypress/e2e/<feature>.cy.js End-to-end flows (prefer one file per feature)
28
+ //
29
+ // Full verify only proves existing specs pass. New UI behavior needs new or
30
+ // updated specs here — the harness does not auto-generate flows per feature.
31
+ // Widen `specPattern` to `cypress/e2e/**/*.cy.{js,jsx,ts,tsx}` when you add
32
+ // TypeScript specs.
33
+ //
34
+ // Artifacts (gitignored under .har/artifacts/)
35
+ // --------------------------------------------
36
+ // cypress-e2e/cypress.log Runner log
37
+ // cypress-e2e/screenshots/ Failure screenshots (cypress run)
38
+ // cypress-e2e/videos/ Videos when `video: true` (off by default)
39
+ //
40
+ // Local-only (without cypress-e2e.sh): npm run test:cypress
41
+ // Set CYPRESS_BASE_URL (and CYPRESS_API_URL if split) when the app is not on
42
+ // the default below.
43
+ const path = require('path');
44
+ const { defineConfig } = require('cypress');
45
+
46
+ const baseUrl = process.env.CYPRESS_BASE_URL || process.env.BASE_URL || 'http://localhost:3000';
47
+ const apiUrl = process.env.CYPRESS_API_URL || process.env.API_URL || baseUrl;
48
+ const healthPath = process.env.CYPRESS_HEALTH_PATH || '/health';
49
+ const artifactDir =
50
+ process.env.HARNESS_CYPRESS_ARTIFACT_DIR || path.join('.har', 'artifacts', 'cypress-e2e');
51
+
52
+ module.exports = defineConfig({
53
+ expose: {
54
+ API_URL: apiUrl,
55
+ HEALTH_PATH: healthPath,
56
+ },
57
+ e2e: {
58
+ baseUrl,
59
+ specPattern: 'cypress/e2e/**/*.cy.js',
60
+ supportFile: 'cypress/support/e2e.js',
61
+ fixturesFolder: false,
62
+ screenshotsFolder: path.join(artifactDir, 'screenshots'),
63
+ videosFolder: path.join(artifactDir, 'videos'),
64
+ downloadsFolder: path.join(artifactDir, 'downloads'),
65
+ video: false,
66
+ screenshotOnRunFailure: true,
67
+ },
68
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "scripts": {
3
+ "test:cypress": "cypress run",
4
+ "test:cypress:open": "cypress open",
5
+ "test:cypress:ci": "cypress run --posix-exit-codes"
6
+ },
7
+ "devDependencies": {
8
+ "cypress": "^16.1.1"
9
+ }
10
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "id": "cypress",
3
+ "stageId": "cypress-e2e",
4
+ "verificationStages": [
5
+ "cypress-e2e"
6
+ ],
7
+ "stage": {
8
+ "id": "cypress-e2e",
9
+ "kind": "test",
10
+ "description": "Cypress end-to-end tests against the slot's running app",
11
+ "script": "stages/cypress-e2e.sh",
12
+ "requiresAgentId": true,
13
+ "artifacts": [
14
+ {
15
+ "path": ".har/artifacts/cypress-e2e",
16
+ "kind": "directory",
17
+ "description": "Cypress run log, screenshots, and videos"
18
+ },
19
+ {
20
+ "path": ".har/artifacts/cypress-e2e/cypress.log",
21
+ "kind": "log"
22
+ }
23
+ ],
24
+ "tier": "full"
25
+ },
26
+ "files": [
27
+ {
28
+ "src": ".har/stages/cypress-e2e.sh",
29
+ "dest": ".har/stages/cypress-e2e.sh",
30
+ "executable": true
31
+ },
32
+ {
33
+ "src": ".har/stages/CYPRESS.md",
34
+ "dest": ".har/stages/CYPRESS.md"
35
+ },
36
+ {
37
+ "src": "cypress.config.js",
38
+ "dest": "cypress.config.js"
39
+ },
40
+ {
41
+ "src": "cypress/e2e/smoke.cy.js",
42
+ "dest": "cypress/e2e/smoke.cy.js"
43
+ },
44
+ {
45
+ "src": "cypress/e2e/api.cy.js",
46
+ "dest": "cypress/e2e/api.cy.js"
47
+ },
48
+ {
49
+ "src": "cypress/support/e2e.js",
50
+ "dest": "cypress/support/e2e.js"
51
+ },
52
+ {
53
+ "src": "README.md",
54
+ "dest": "cypress/README.md"
55
+ }
56
+ ],
57
+ "optionalFiles": [
58
+ {
59
+ "src": ".github/workflows/cypress.yml",
60
+ "dest": ".github/workflows/cypress.yml",
61
+ "skipFlag": "skipCi"
62
+ }
63
+ ],
64
+ "merge": {
65
+ "package.json": "package.fragment.json"
66
+ },
67
+ "nextSteps": [
68
+ "npm install",
69
+ "npx cypress verify",
70
+ "har env launch 1",
71
+ "./.har/stages/cypress-e2e.sh 1"
72
+ ],
73
+ "docsPath": ".har/stages/CYPRESS.md"
74
+ }
@@ -104,7 +104,8 @@ committed.
104
104
  playwright` (web), `har env add-plugin rocketsim` (iOS), or `har env
105
105
  add-plugin kerno` (backend), `har env add-plugin gitleaks` (secrets scanning, any
106
106
  stack), or `har env add-plugin trivy` (dependency + IaC security scan, any stack),
107
- or `har env add-plugin semgrep` (SAST, any stack)
107
+ or `har env add-plugin semgrep` (SAST, any stack),
108
+ or `har env add-plugin cypress` (browser e2e)
108
109
  installs one. A
109
110
  plugin is just packaging: it copies files, merges `package.json` fragments,
110
111
  and registers stages through the exact same registry as everything else.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osfactory/har",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "HAR: open agent harness CLI and MCP server for coding agents, isolated worktrees, and software-factory workflows",
5
5
  "keywords": [
6
6
  "har",