@osfactory/har 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/LICENSE +674 -0
- package/README.md +208 -0
- package/control/docker-compose.build.yml +7 -0
- package/control/docker-compose.yml +31 -0
- package/dist/index.js +41753 -0
- package/dist/prompts/system-authoring.md +104 -0
- package/dist/templates/AGENT.md.template +40 -0
- package/dist/templates/CLAUDE.md.template +5 -0
- package/dist/templates/adaptation-prompt-init.md +124 -0
- package/dist/templates/adaptation-prompt-maintain.md +73 -0
- package/dist/templates/cursor-rule.mdc.template +101 -0
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +54 -0
- package/dist/templates/har-boilerplate/README.md +134 -0
- package/dist/templates/har-boilerplate/agent-cli.sh +172 -0
- package/dist/templates/har-boilerplate/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate/attach.sh +16 -0
- package/dist/templates/har-boilerplate/docker-compose.agent.yml +66 -0
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +44 -0
- package/dist/templates/har-boilerplate/env.template +37 -0
- package/dist/templates/har-boilerplate/harness.env +50 -0
- package/dist/templates/har-boilerplate/justfile +39 -0
- package/dist/templates/har-boilerplate/launch.sh +229 -0
- package/dist/templates/har-boilerplate/setup-infra.sh +102 -0
- package/dist/templates/har-boilerplate/stages/README.sh +9 -0
- package/dist/templates/har-boilerplate/stages.json +67 -0
- package/dist/templates/har-boilerplate/teardown.sh +86 -0
- package/dist/templates/har-boilerplate/verify.sh +163 -0
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +41 -0
- package/dist/templates/har-boilerplate-cli/README.md +112 -0
- package/dist/templates/har-boilerplate-cli/agent-cli.sh +162 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +64 -0
- package/dist/templates/har-boilerplate-cli/harness.env +40 -0
- package/dist/templates/har-boilerplate-cli/justfile +27 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +132 -0
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +102 -0
- package/dist/templates/har-boilerplate-cli/stages/README.sh +9 -0
- package/dist/templates/har-boilerplate-cli/stages.json +61 -0
- package/dist/templates/har-boilerplate-cli/teardown.sh +66 -0
- package/dist/templates/har-boilerplate-cli/verify.sh +113 -0
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +57 -0
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +65 -0
- package/dist/templates/har-boilerplate-ios/README.md +88 -0
- package/dist/templates/har-boilerplate-ios/agent-cli.sh +125 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +270 -0
- package/dist/templates/har-boilerplate-ios/docker-compose.agent.yml +17 -0
- package/dist/templates/har-boilerplate-ios/harness.env +42 -0
- package/dist/templates/har-boilerplate-ios/justfile +33 -0
- package/dist/templates/har-boilerplate-ios/launch.sh +132 -0
- package/dist/templates/har-boilerplate-ios/setup-infra.sh +84 -0
- package/dist/templates/har-boilerplate-ios/stages/README.sh +12 -0
- package/dist/templates/har-boilerplate-ios/stages.json +61 -0
- package/dist/templates/har-boilerplate-ios/teardown.sh +64 -0
- package/dist/templates/har-boilerplate-ios/verify.sh +165 -0
- package/dist/templates/stage-templates/playwright/.github/workflows/playwright.yml +30 -0
- package/dist/templates/stage-templates/playwright/.har/stages/PLAYWRIGHT.md +21 -0
- package/dist/templates/stage-templates/playwright/.har/stages/browser-e2e.sh +83 -0
- package/dist/templates/stage-templates/playwright/README.md +14 -0
- package/dist/templates/stage-templates/playwright/package.fragment.json +14 -0
- package/dist/templates/stage-templates/playwright/playwright.config.js +30 -0
- package/dist/templates/stage-templates/playwright/template.manifest.json +44 -0
- package/dist/templates/stage-templates/playwright/tests/a11y/smoke.spec.js +14 -0
- package/dist/templates/stage-templates/playwright/tests/api/health.spec.js +10 -0
- package/dist/templates/stage-templates/playwright/tests/frontend/smoke.spec.js +9 -0
- package/dist/templates/stage-templates/rocketsim/.har/stages/ROCKETSIM.md +126 -0
- package/dist/templates/stage-templates/rocketsim/.har/stages/rocketsim-flows.sh +188 -0
- package/dist/templates/stage-templates/rocketsim/README.md +66 -0
- package/dist/templates/stage-templates/rocketsim/flows/README.md +36 -0
- package/dist/templates/stage-templates/rocketsim/flows/example-smoke.sh +60 -0
- package/dist/templates/stage-templates/rocketsim/template.manifest.json +38 -0
- package/package.json +78 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tear down an agent slot for iOS mobile app repos.
|
|
3
|
+
# The session's git branch is KEPT by default so you can push it / open a PR —
|
|
4
|
+
# pass --delete-branch to remove it too.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ./.har/teardown.sh <agent-id> [--delete-branch]
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
|
|
12
|
+
# shellcheck source=/dev/null
|
|
13
|
+
source "$SCRIPT_DIR/harness.env"
|
|
14
|
+
# shellcheck source=/dev/null
|
|
15
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
16
|
+
|
|
17
|
+
AGENT_ID="${1:?Usage: teardown.sh <agent-id> [--delete-branch]}"
|
|
18
|
+
DELETE_BRANCH=false
|
|
19
|
+
|
|
20
|
+
for arg in "${@:2}"; do
|
|
21
|
+
case "$arg" in
|
|
22
|
+
--delete-branch) DELETE_BRANCH=true ;;
|
|
23
|
+
esac
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
validate_agent_id "$AGENT_ID"
|
|
27
|
+
|
|
28
|
+
echo "==> Tearing down agent ${AGENT_ID}..."
|
|
29
|
+
|
|
30
|
+
REGISTRY_FILE="$(slot_registry_file "$AGENT_ID")"
|
|
31
|
+
WORKTREE_PATH=""
|
|
32
|
+
WORK_DIR=""
|
|
33
|
+
BRANCH=""
|
|
34
|
+
if [ -f "$REGISTRY_FILE" ]; then
|
|
35
|
+
WORKTREE_PATH="$(read_slot_field "$REGISTRY_FILE" worktreePath || true)"
|
|
36
|
+
WORK_DIR="$(read_slot_field "$REGISTRY_FILE" workDir || true)"
|
|
37
|
+
BRANCH="$(read_slot_field "$REGISTRY_FILE" branch || true)"
|
|
38
|
+
fi
|
|
39
|
+
[ -n "$WORKTREE_PATH" ] || WORKTREE_PATH="$HOME/worktrees/${HARNESS_PROJECT_NAME}-agent-${AGENT_ID}"
|
|
40
|
+
|
|
41
|
+
rm -f "$REPO_ROOT/.env.agent.${AGENT_ID}"
|
|
42
|
+
if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
|
|
43
|
+
rm -f "$WORK_DIR/.env.agent.${AGENT_ID}"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [ -d "$WORKTREE_PATH" ]; then
|
|
47
|
+
rm -f "$WORKTREE_PATH/.env.agent.${AGENT_ID}"
|
|
48
|
+
git -C "$REPO_ROOT" worktree remove "$WORKTREE_PATH" --force 2>/dev/null || rm -rf "$WORKTREE_PATH"
|
|
49
|
+
echo "✓ Removed worktree: $WORKTREE_PATH"
|
|
50
|
+
fi
|
|
51
|
+
git -C "$REPO_ROOT" worktree prune 2>/dev/null || true
|
|
52
|
+
|
|
53
|
+
if [ -n "$BRANCH" ]; then
|
|
54
|
+
if [ "$DELETE_BRANCH" = true ]; then
|
|
55
|
+
git -C "$REPO_ROOT" branch -D "$BRANCH" 2>/dev/null || true
|
|
56
|
+
echo "✓ Deleted branch: $BRANCH"
|
|
57
|
+
else
|
|
58
|
+
echo "✓ Kept branch: $BRANCH (push it or delete with: git branch -D $BRANCH)"
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
remove_slot_registry "$AGENT_ID"
|
|
63
|
+
|
|
64
|
+
echo "✓ Agent ${AGENT_ID} torn down"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Verification pipeline for iOS mobile app repos — build and test with xcodebuild.
|
|
3
|
+
# Outputs JSON to stdout, human-readable progress to stderr.
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./.har/verify.sh <agent-id> [--full]
|
|
6
|
+
#
|
|
7
|
+
# Quick (default): build + unit-tests
|
|
8
|
+
# Full (--full): + lint (SwiftLint) + rocketsim-flows (if installed)
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
|
|
14
|
+
# shellcheck source=/dev/null
|
|
15
|
+
source "$SCRIPT_DIR/harness.env"
|
|
16
|
+
# shellcheck source=/dev/null
|
|
17
|
+
source "$SCRIPT_DIR/agent-slot.sh"
|
|
18
|
+
|
|
19
|
+
AGENT_ID="${1:?Usage: verify.sh <agent-id> [--full]}"
|
|
20
|
+
FULL=""
|
|
21
|
+
|
|
22
|
+
for arg in "${@:2}"; do
|
|
23
|
+
[ "$arg" = "--full" ] && FULL=1
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
validate_agent_id "$AGENT_ID"
|
|
27
|
+
|
|
28
|
+
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT")" || {
|
|
29
|
+
echo "No .env.agent.${AGENT_ID} found. Run: ./.har/launch.sh ${AGENT_ID}" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set -a
|
|
34
|
+
# shellcheck source=/dev/null
|
|
35
|
+
source "$ENV_FILE"
|
|
36
|
+
set +a
|
|
37
|
+
|
|
38
|
+
WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
|
|
39
|
+
|
|
40
|
+
echo "==> Verifying agent ${AGENT_ID} in ${WORK_DIR}..." >&2
|
|
41
|
+
|
|
42
|
+
# Build xcodebuild project/workspace flags
|
|
43
|
+
xc_target_flags() {
|
|
44
|
+
if [ -n "${HARNESS_XCODE_WORKSPACE:-}" ] && [ -e "$WORK_DIR/${HARNESS_XCODE_WORKSPACE}" ]; then
|
|
45
|
+
echo "-workspace $WORK_DIR/${HARNESS_XCODE_WORKSPACE}"
|
|
46
|
+
elif [ -n "${HARNESS_XCODE_PROJECT:-}" ] && [ -e "$WORK_DIR/${HARNESS_XCODE_PROJECT}" ]; then
|
|
47
|
+
echo "-project $WORK_DIR/${HARNESS_XCODE_PROJECT}"
|
|
48
|
+
else
|
|
49
|
+
# Auto-detect: prefer workspace (CocoaPods), then project
|
|
50
|
+
local ws prj
|
|
51
|
+
ws="$(find "$WORK_DIR" -maxdepth 2 -name "*.xcworkspace" ! -path "*/\.*" 2>/dev/null | head -1 || true)"
|
|
52
|
+
prj="$(find "$WORK_DIR" -maxdepth 2 -name "*.xcodeproj" ! -path "*/\.*" 2>/dev/null | head -1 || true)"
|
|
53
|
+
if [ -n "$ws" ]; then echo "-workspace $ws"
|
|
54
|
+
elif [ -n "$prj" ]; then echo "-project $prj"
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
XC_FLAGS="$(xc_target_flags)"
|
|
60
|
+
XC_SCHEME="${HARNESS_XCODE_SCHEME:-MyApp}"
|
|
61
|
+
XC_DESTINATION="${HARNESS_IOS_DESTINATION:-platform=iOS Simulator,name=iPhone 16}"
|
|
62
|
+
XC_DERIVED="${WORK_DIR}/build/DerivedData"
|
|
63
|
+
|
|
64
|
+
OVERALL_PASS=true
|
|
65
|
+
START_TOTAL=$(date +%s%3N 2>/dev/null || echo "0")
|
|
66
|
+
RESULTS_JSON="[]"
|
|
67
|
+
|
|
68
|
+
run_step() {
|
|
69
|
+
local name="$1"
|
|
70
|
+
local cmd="$2"
|
|
71
|
+
local start end elapsed exit_code output
|
|
72
|
+
|
|
73
|
+
printf " → %-40s" "$name..." >&2
|
|
74
|
+
start=$(date +%s%3N 2>/dev/null || echo "0")
|
|
75
|
+
|
|
76
|
+
set +e
|
|
77
|
+
output=$(cd "$WORK_DIR" && eval "$cmd" 2>&1)
|
|
78
|
+
exit_code=$?
|
|
79
|
+
set -e
|
|
80
|
+
|
|
81
|
+
end=$(date +%s%3N 2>/dev/null || echo "0")
|
|
82
|
+
elapsed=$(( end - start ))
|
|
83
|
+
|
|
84
|
+
local pass_bool step_output_escaped
|
|
85
|
+
if [ "$exit_code" = "0" ]; then
|
|
86
|
+
echo "✓ (${elapsed}ms)" >&2
|
|
87
|
+
pass_bool="true"
|
|
88
|
+
else
|
|
89
|
+
echo "✗ (${elapsed}ms)" >&2
|
|
90
|
+
echo "$output" | head -40 | sed 's/^/ /' >&2
|
|
91
|
+
pass_bool="false"
|
|
92
|
+
OVERALL_PASS=false
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
step_output_escaped=$(echo "$output" | head -50 | \
|
|
96
|
+
node -e "let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>process.stdout.write(JSON.stringify(d.trim())))" \
|
|
97
|
+
2>/dev/null || echo '""')
|
|
98
|
+
|
|
99
|
+
RESULTS_JSON=$(echo "$RESULTS_JSON" | node -e "
|
|
100
|
+
const fs = require('fs');
|
|
101
|
+
let arr = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
|
|
102
|
+
arr.push({name:'$name',pass:$pass_bool,ms:$elapsed,output:$step_output_escaped});
|
|
103
|
+
process.stdout.write(JSON.stringify(arr));
|
|
104
|
+
" 2>/dev/null || echo "$RESULTS_JSON")
|
|
105
|
+
|
|
106
|
+
if [ "$pass_bool" = "false" ] && [ -z "$FULL" ]; then
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Build (compile-only — catches syntax errors and missing symbols)
|
|
112
|
+
run_step "build" "xcodebuild build \
|
|
113
|
+
${XC_FLAGS} \
|
|
114
|
+
-scheme \"${XC_SCHEME}\" \
|
|
115
|
+
-destination \"${XC_DESTINATION}\" \
|
|
116
|
+
-derivedDataPath \"${XC_DERIVED}\" \
|
|
117
|
+
CODE_SIGNING_ALLOWED=NO \
|
|
118
|
+
| xcbeautify 2>/dev/null || xcodebuild build \
|
|
119
|
+
${XC_FLAGS} \
|
|
120
|
+
-scheme \"${XC_SCHEME}\" \
|
|
121
|
+
-destination \"${XC_DESTINATION}\" \
|
|
122
|
+
-derivedDataPath \"${XC_DERIVED}\" \
|
|
123
|
+
CODE_SIGNING_ALLOWED=NO" || { [ -z "$FULL" ] && true; }
|
|
124
|
+
|
|
125
|
+
# Unit tests
|
|
126
|
+
run_step "unit-tests" "xcodebuild test \
|
|
127
|
+
${XC_FLAGS} \
|
|
128
|
+
-scheme \"${XC_SCHEME}\" \
|
|
129
|
+
-destination \"${XC_DESTINATION}\" \
|
|
130
|
+
-derivedDataPath \"${XC_DERIVED}\" \
|
|
131
|
+
CODE_SIGNING_ALLOWED=NO \
|
|
132
|
+
| xcbeautify 2>/dev/null || xcodebuild test \
|
|
133
|
+
${XC_FLAGS} \
|
|
134
|
+
-scheme \"${XC_SCHEME}\" \
|
|
135
|
+
-destination \"${XC_DESTINATION}\" \
|
|
136
|
+
-derivedDataPath \"${XC_DERIVED}\" \
|
|
137
|
+
CODE_SIGNING_ALLOWED=NO" || { [ -z "$FULL" ] && true; }
|
|
138
|
+
|
|
139
|
+
if [ -n "$FULL" ]; then
|
|
140
|
+
# SwiftLint — optional, skip gracefully when not installed
|
|
141
|
+
run_step "lint" "command -v \"${HARNESS_SWIFTLINT_CMD:-swiftlint}\" >/dev/null 2>&1 && \
|
|
142
|
+
\"${HARNESS_SWIFTLINT_CMD:-swiftlint}\" --quiet 2>&1 || echo 'swiftlint not installed — skipping'" || true
|
|
143
|
+
|
|
144
|
+
# RocketSim user-flow validation — installed via: har env add-stage rocketsim
|
|
145
|
+
run_rocketsim_flows_if_present "$SCRIPT_DIR" "$AGENT_ID" || true
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
END_TOTAL=$(date +%s%3N 2>/dev/null || echo "0")
|
|
149
|
+
TOTAL_MS=$(( END_TOTAL - START_TOTAL ))
|
|
150
|
+
|
|
151
|
+
node -e "
|
|
152
|
+
const results = $RESULTS_JSON;
|
|
153
|
+
const overall = results.length > 0 && results.every(r => r.pass);
|
|
154
|
+
const out = {
|
|
155
|
+
status: overall ? 'pass' : 'fail',
|
|
156
|
+
agent_id: $AGENT_ID,
|
|
157
|
+
total_ms: $TOTAL_MS,
|
|
158
|
+
stages: results,
|
|
159
|
+
};
|
|
160
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
161
|
+
" 2>/dev/null || echo "{\"status\":\"fail\",\"agent_id\":${AGENT_ID},\"stages\":[]}"
|
|
162
|
+
|
|
163
|
+
if [ "$OVERALL_PASS" = "false" ]; then
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Playwright tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- name: Setup Node
|
|
13
|
+
uses: actions/setup-node@v4
|
|
14
|
+
with:
|
|
15
|
+
node-version: 18
|
|
16
|
+
- name: Install deps
|
|
17
|
+
run: npm ci
|
|
18
|
+
- name: Install browsers
|
|
19
|
+
run: npx playwright install --with-deps
|
|
20
|
+
- name: Run Playwright tests
|
|
21
|
+
env:
|
|
22
|
+
BASE_URL: ${{ secrets.BASE_URL }}
|
|
23
|
+
TEST_RESET_API: 'true'
|
|
24
|
+
run: npm run test:e2e:ci
|
|
25
|
+
- name: Upload Playwright HTML report
|
|
26
|
+
if: success() || failure()
|
|
27
|
+
uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: playwright-report
|
|
30
|
+
path: .har/artifacts/browser-e2e/playwright-report
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Playwright (browser-e2e)
|
|
2
|
+
|
|
3
|
+
Adapt specs under `tests/` for your application. Full verification (`verify --full`) runs this stage when `stages/browser-e2e.sh` is present.
|
|
4
|
+
|
|
5
|
+
| Directory | Purpose |
|
|
6
|
+
|-----------|---------|
|
|
7
|
+
| `tests/frontend/` | UI smoke and flows — extend when you add UI |
|
|
8
|
+
| `tests/api/` | HTTP checks via Playwright `request` |
|
|
9
|
+
| `tests/a11y/` | axe-core on key routes |
|
|
10
|
+
|
|
11
|
+
## Run
|
|
12
|
+
|
|
13
|
+
After `./.har/launch.sh <id>`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
./.har/stages/browser-e2e.sh <id>
|
|
17
|
+
# included in:
|
|
18
|
+
./.har/verify.sh <id> --full
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Replace TODO selectors and paths in the scaffold specs during harness adaptation.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Playwright browser/API/a11y tests for an agent slot.
|
|
3
|
+
# Outputs JSON to stdout, human-readable progress to stderr.
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./.har/stages/browser-e2e.sh <agent-id>
|
|
6
|
+
# Prerequisite: ./.har/launch.sh <agent-id>
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
HARNESS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
REPO_ROOT="$(cd "$HARNESS_DIR/.." && pwd)"
|
|
12
|
+
|
|
13
|
+
# shellcheck source=/dev/null
|
|
14
|
+
source "$HARNESS_DIR/harness.env"
|
|
15
|
+
# shellcheck source=/dev/null
|
|
16
|
+
source "$HARNESS_DIR/agent-slot.sh"
|
|
17
|
+
|
|
18
|
+
AGENT_ID="${1:?Usage: browser-e2e.sh <agent-id>}"
|
|
19
|
+
validate_agent_id "$AGENT_ID"
|
|
20
|
+
|
|
21
|
+
log() { echo "==> [browser-e2e agent-$AGENT_ID] $*" >&2; }
|
|
22
|
+
|
|
23
|
+
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT")" || {
|
|
24
|
+
echo "No .env.agent.${AGENT_ID} found. Run: ./.har/launch.sh ${AGENT_ID}" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set -a
|
|
29
|
+
# shellcheck source=/dev/null
|
|
30
|
+
source "$ENV_FILE"
|
|
31
|
+
set +a
|
|
32
|
+
|
|
33
|
+
WORK_DIR="$(resolve_agent_work_dir "$ENV_FILE")"
|
|
34
|
+
FE_PORT="${FE_PORT:-$(( HARNESS_FE_BASE_PORT + AGENT_ID * 10 ))}"
|
|
35
|
+
API_PORT="${API_PORT:-$(( HARNESS_API_BASE_PORT + AGENT_ID * 10 ))}"
|
|
36
|
+
|
|
37
|
+
export BASE_URL="${BASE_URL:-http://localhost:${FE_PORT}}"
|
|
38
|
+
export API_URL="${API_URL:-http://localhost:${API_PORT}}"
|
|
39
|
+
|
|
40
|
+
ARTIFACT_DIR="$REPO_ROOT/.har/artifacts/browser-e2e"
|
|
41
|
+
mkdir -p "$ARTIFACT_DIR"
|
|
42
|
+
|
|
43
|
+
log "Running Playwright against $BASE_URL (API: $API_URL)"
|
|
44
|
+
log "Work dir: $WORK_DIR"
|
|
45
|
+
|
|
46
|
+
START_TOTAL=$(date +%s%3N 2>/dev/null || echo "0")
|
|
47
|
+
|
|
48
|
+
set +e
|
|
49
|
+
cd "$WORK_DIR"
|
|
50
|
+
PW_OUTPUT=$(npx playwright test 2>&1)
|
|
51
|
+
PW_EXIT=$?
|
|
52
|
+
set -e
|
|
53
|
+
|
|
54
|
+
END_TOTAL=$(date +%s%3N 2>/dev/null || echo "0")
|
|
55
|
+
TOTAL_MS=$(( END_TOTAL - START_TOTAL ))
|
|
56
|
+
|
|
57
|
+
echo "$PW_OUTPUT" >&2
|
|
58
|
+
|
|
59
|
+
REPORT_DIR="$ARTIFACT_DIR/playwright-report"
|
|
60
|
+
REPORT_INDEX="$REPORT_DIR/index.html"
|
|
61
|
+
|
|
62
|
+
node -e "
|
|
63
|
+
const out = {
|
|
64
|
+
status: ${PW_EXIT} === 0 ? 'pass' : 'fail',
|
|
65
|
+
stageId: 'browser-e2e',
|
|
66
|
+
kind: 'test',
|
|
67
|
+
agent_id: ${AGENT_ID},
|
|
68
|
+
total_ms: ${TOTAL_MS},
|
|
69
|
+
urls: [
|
|
70
|
+
{ label: 'frontend', url: process.env.BASE_URL || '${BASE_URL}' },
|
|
71
|
+
{ label: 'api', url: process.env.API_URL || '${API_URL}' },
|
|
72
|
+
],
|
|
73
|
+
artifacts: [
|
|
74
|
+
{ path: '.har/artifacts/browser-e2e', kind: 'directory' },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
if (require('fs').existsSync('${REPORT_INDEX}')) {
|
|
78
|
+
out.artifacts.push({ path: '.har/artifacts/browser-e2e/playwright-report', kind: 'report' });
|
|
79
|
+
}
|
|
80
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
81
|
+
"
|
|
82
|
+
|
|
83
|
+
exit "$PW_EXIT"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Playwright HAR stage template
|
|
2
|
+
|
|
3
|
+
This template was added by `har env add-stage playwright`. It registers a `browser-e2e` stage and scaffolds a minimal Playwright test suite.
|
|
4
|
+
|
|
5
|
+
## Next steps
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npx playwright install
|
|
10
|
+
./.har/launch.sh 1
|
|
11
|
+
./.har/stages/browser-e2e.sh 1
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
See `.har/stages/PLAYWRIGHT.md` for adaptation checklist and layout.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"test:e2e": "playwright test",
|
|
4
|
+
"test:e2e:ui": "playwright test tests/frontend",
|
|
5
|
+
"test:e2e:api": "playwright test tests/api",
|
|
6
|
+
"test:e2e:a11y": "playwright test tests/a11y",
|
|
7
|
+
"test:e2e:headed": "playwright test --headed",
|
|
8
|
+
"test:e2e:ci": "playwright test --reporter=html --workers=2 --retries=2"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@playwright/test": "^1.40.0",
|
|
12
|
+
"axe-core": "^4.7.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Playwright config tuned for HAR agent slots and CI.
|
|
2
|
+
const { defineConfig, devices } = require('@playwright/test');
|
|
3
|
+
|
|
4
|
+
module.exports = defineConfig({
|
|
5
|
+
testDir: './tests',
|
|
6
|
+
timeout: 30 * 1000,
|
|
7
|
+
expect: { timeout: 5000 },
|
|
8
|
+
fullyParallel: true,
|
|
9
|
+
forbidOnly: !!process.env.CI,
|
|
10
|
+
retries: process.env.CI ? 2 : 0,
|
|
11
|
+
workers: process.env.CI ? 2 : undefined,
|
|
12
|
+
reporter: [
|
|
13
|
+
['list'],
|
|
14
|
+
['html', { open: 'never', outputFolder: '.har/artifacts/browser-e2e/playwright-report' }],
|
|
15
|
+
],
|
|
16
|
+
outputDir: '.har/artifacts/browser-e2e/test-results',
|
|
17
|
+
use: {
|
|
18
|
+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
19
|
+
headless: true,
|
|
20
|
+
screenshot: 'only-on-failure',
|
|
21
|
+
video: 'retain-on-failure',
|
|
22
|
+
trace: 'on-first-retry',
|
|
23
|
+
},
|
|
24
|
+
projects: [
|
|
25
|
+
{
|
|
26
|
+
name: 'chromium',
|
|
27
|
+
use: { ...devices['Desktop Chrome'] },
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "playwright",
|
|
3
|
+
"stageId": "browser-e2e",
|
|
4
|
+
"verificationStages": [
|
|
5
|
+
"typecheck",
|
|
6
|
+
"unit-tests",
|
|
7
|
+
"api-health",
|
|
8
|
+
"lint",
|
|
9
|
+
"browser-e2e"
|
|
10
|
+
],
|
|
11
|
+
"stage": {
|
|
12
|
+
"id": "browser-e2e",
|
|
13
|
+
"kind": "test",
|
|
14
|
+
"description": "Playwright E2E (frontend + API + a11y)",
|
|
15
|
+
"script": "stages/browser-e2e.sh",
|
|
16
|
+
"requiresAgentId": true,
|
|
17
|
+
"artifacts": [
|
|
18
|
+
{
|
|
19
|
+
"path": ".har/artifacts/browser-e2e",
|
|
20
|
+
"kind": "directory",
|
|
21
|
+
"description": "HTML report, traces, screenshots, videos"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": ".har/artifacts/browser-e2e/playwright-report",
|
|
25
|
+
"kind": "report"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
{ "src": ".har/stages/browser-e2e.sh", "dest": ".har/stages/browser-e2e.sh", "executable": true },
|
|
31
|
+
{ "src": ".har/stages/PLAYWRIGHT.md", "dest": ".har/stages/PLAYWRIGHT.md" },
|
|
32
|
+
{ "src": "playwright.config.js", "dest": "playwright.config.js" },
|
|
33
|
+
{ "src": "tests/frontend/smoke.spec.js", "dest": "tests/frontend/smoke.spec.js" },
|
|
34
|
+
{ "src": "tests/api/health.spec.js", "dest": "tests/api/health.spec.js" },
|
|
35
|
+
{ "src": "tests/a11y/smoke.spec.js", "dest": "tests/a11y/smoke.spec.js" },
|
|
36
|
+
{ "src": "README.md", "dest": "tests/playwright/README.md" }
|
|
37
|
+
],
|
|
38
|
+
"optionalFiles": [
|
|
39
|
+
{ "src": ".github/workflows/playwright.yml", "dest": ".github/workflows/playwright.yml", "skipFlag": "skipCi" }
|
|
40
|
+
],
|
|
41
|
+
"merge": {
|
|
42
|
+
"package.json": "package.fragment.json"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { test, expect } = require('@playwright/test');
|
|
2
|
+
|
|
3
|
+
test('homepage passes axe critical/serious checks', async ({ page }) => {
|
|
4
|
+
await page.goto('/');
|
|
5
|
+
await page.addScriptTag({ path: require.resolve('axe-core/axe.min.js') });
|
|
6
|
+
const results = await page.evaluate(async () => {
|
|
7
|
+
// eslint-disable-next-line no-undef
|
|
8
|
+
return await axe.run();
|
|
9
|
+
});
|
|
10
|
+
const blocking = results.violations.filter(
|
|
11
|
+
(v) => v.impact === 'critical' || v.impact === 'serious',
|
|
12
|
+
);
|
|
13
|
+
expect(blocking).toEqual([]);
|
|
14
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { test, expect } = require('@playwright/test');
|
|
2
|
+
|
|
3
|
+
// TODO: adapt path and expected body for your app's health endpoint
|
|
4
|
+
test.describe('API smoke', () => {
|
|
5
|
+
test('health endpoint responds', async ({ request }) => {
|
|
6
|
+
const healthPath = process.env.HARNESS_HEALTH_PATH || '/health';
|
|
7
|
+
const res = await request.get(healthPath);
|
|
8
|
+
expect(res.ok()).toBeTruthy();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const { test, expect } = require('@playwright/test');
|
|
2
|
+
|
|
3
|
+
// TODO: adapt heading text and routes for your app
|
|
4
|
+
test.describe('Frontend smoke', () => {
|
|
5
|
+
test('homepage loads', async ({ page }) => {
|
|
6
|
+
await page.goto('/');
|
|
7
|
+
await expect(page.locator('body')).toBeVisible();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# RocketSim User-Flow Validation
|
|
2
|
+
|
|
3
|
+
The `rocketsim-flows` stage runs reusable bash scripts from `flows/` that interact with your iOS app on the simulator and validate its state. Full verify (`verify --full`) runs all flows automatically.
|
|
4
|
+
|
|
5
|
+
## Flow script contract
|
|
6
|
+
|
|
7
|
+
A flow is a bash script (`flows/<name>.sh`) that:
|
|
8
|
+
|
|
9
|
+
1. Uses the `rocketsim` CLI to inspect the UI, interact with the app, and take screenshots
|
|
10
|
+
2. Exits **0** on success (all assertions passed), **non-zero** on failure
|
|
11
|
+
3. Writes human-readable progress to **stderr**
|
|
12
|
+
4. Optionally saves screenshots to `$FLOW_ARTIFACT_DIR` (provided by the runner)
|
|
13
|
+
|
|
14
|
+
The runner sets these environment variables for each flow:
|
|
15
|
+
| Variable | Value |
|
|
16
|
+
|----------|-------|
|
|
17
|
+
| `FLOW_ARTIFACT_DIR` | Directory for screenshots and output of this flow |
|
|
18
|
+
| `WORK_DIR` | Path to the agent's session worktree |
|
|
19
|
+
| `HARNESS_BUNDLE_ID` | App bundle identifier (from `harness.env`) |
|
|
20
|
+
| `HARNESS_SIMULATOR_NAME` | Target simulator name (from `harness.env`) |
|
|
21
|
+
| `AGENT_ID` | Active agent slot id |
|
|
22
|
+
|
|
23
|
+
## Key RocketSim CLI commands
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Health check
|
|
27
|
+
rocketsim doctor
|
|
28
|
+
|
|
29
|
+
# Check which simulator is focused
|
|
30
|
+
rocketsim simulator focused
|
|
31
|
+
|
|
32
|
+
# Read visible UI elements (compact format for agents)
|
|
33
|
+
rocketsim elements --agent
|
|
34
|
+
|
|
35
|
+
# Wait for screen to change after an interaction
|
|
36
|
+
rocketsim wait screen-changed
|
|
37
|
+
|
|
38
|
+
# Wait for a specific element to appear
|
|
39
|
+
rocketsim wait element --label "Continue"
|
|
40
|
+
|
|
41
|
+
# Tap by element label
|
|
42
|
+
rocketsim interact tap --label "Continue"
|
|
43
|
+
|
|
44
|
+
# Tap by type + label (more precise)
|
|
45
|
+
rocketsim interact tap --type Button --label "Submit"
|
|
46
|
+
|
|
47
|
+
# Tap at coordinates
|
|
48
|
+
rocketsim interact tap 210 642
|
|
49
|
+
|
|
50
|
+
# Swipe
|
|
51
|
+
rocketsim interact swipe --direction up
|
|
52
|
+
rocketsim interact swipe --from 200,650 --to 200,150
|
|
53
|
+
|
|
54
|
+
# Type text
|
|
55
|
+
rocketsim interact type "hello@example.com"
|
|
56
|
+
|
|
57
|
+
# Press hardware button
|
|
58
|
+
rocketsim interact button home
|
|
59
|
+
|
|
60
|
+
# Take a screenshot (PNG bytes to stdout)
|
|
61
|
+
rocketsim screenshot > "$FLOW_ARTIFACT_DIR/screen.png"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Writing a new flow
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
#!/usr/bin/env bash
|
|
68
|
+
# flows/my-feature.sh — Validate MyFeature after changes
|
|
69
|
+
set -euo pipefail
|
|
70
|
+
|
|
71
|
+
log() { echo "==> [my-feature] $*" >&2; }
|
|
72
|
+
|
|
73
|
+
# 1. Snapshot the current screen
|
|
74
|
+
rocketsim wait screen-changed --timeout 5 2>/dev/null || true
|
|
75
|
+
ELEMENTS=$(rocketsim elements --agent)
|
|
76
|
+
|
|
77
|
+
# 2. Assert expected element is present
|
|
78
|
+
if echo "$ELEMENTS" | grep -q "My Feature Button"; then
|
|
79
|
+
log "✓ My Feature Button visible"
|
|
80
|
+
else
|
|
81
|
+
log "✗ My Feature Button not found"
|
|
82
|
+
rocketsim screenshot > "$FLOW_ARTIFACT_DIR/failure.png"
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# 3. Interact
|
|
87
|
+
rocketsim interact tap --label "My Feature Button"
|
|
88
|
+
rocketsim wait screen-changed
|
|
89
|
+
|
|
90
|
+
# 4. Assert outcome
|
|
91
|
+
AFTER=$(rocketsim elements --agent)
|
|
92
|
+
if echo "$AFTER" | grep -q "Expected Result Label"; then
|
|
93
|
+
log "✓ Expected result is visible"
|
|
94
|
+
rocketsim screenshot > "$FLOW_ARTIFACT_DIR/success.png"
|
|
95
|
+
else
|
|
96
|
+
log "✗ Expected result not visible"
|
|
97
|
+
rocketsim screenshot > "$FLOW_ARTIFACT_DIR/failure.png"
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Running flows
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# All flows
|
|
106
|
+
./.har/stages/rocketsim-flows.sh 1
|
|
107
|
+
|
|
108
|
+
# Single flow
|
|
109
|
+
./.har/stages/rocketsim-flows.sh 1 example-smoke
|
|
110
|
+
|
|
111
|
+
# Included in full verify
|
|
112
|
+
./.har/verify.sh 1 --full
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Screenshots and per-flow logs are saved under `.har/artifacts/rocketsim-flows/`.
|
|
116
|
+
|
|
117
|
+
## Guidelines for agents creating flows
|
|
118
|
+
|
|
119
|
+
- **One flow per user journey** — keep flows focused: login, onboarding, settings, a specific feature.
|
|
120
|
+
- **Validate after each action** — use `rocketsim wait screen-changed` or `wait element` before asserting.
|
|
121
|
+
- **Take a screenshot on failure** — always save `$FLOW_ARTIFACT_DIR/failure.png` on assertion failures so humans can diagnose problems.
|
|
122
|
+
- **Take a screenshot on success** — save `$FLOW_ARTIFACT_DIR/success.png` as visual confirmation.
|
|
123
|
+
- **Use label-based selectors** — prefer `--label` over raw coordinates; coordinates break when layout changes.
|
|
124
|
+
- **Reset state** — if the flow navigates away from the start screen, navigate back at the end.
|
|
125
|
+
- **Name flows for their user journey** — `onboarding.sh`, `login.sh`, `settings-notification-toggle.sh`.
|
|
126
|
+
- **Add a new flow for every new UI feature** — this ensures regressions are caught on future changes.
|