@ihyeon/flow-qa 0.1.3 → 0.2.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.ko.md +9 -3
- package/README.md +9 -3
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/src/cli.js +42 -8
- package/dist/src/compiler.js +11 -3
- package/dist/src/engine.js +62 -10
- package/dist/src/i18n.js +13 -0
- package/dist/src/mobile-runtime.js +66 -0
- package/dist/src/planner.js +19 -7
- package/dist/src/report.js +4 -1
- package/dist/src/schema.js +21 -1
- package/dist/src/targets.js +20 -0
- package/dist/src/ui/public/app.js +61 -17
- package/dist/src/ui/public/i18n.js +27 -3
- package/dist/src/ui/public/index.html +6 -2
- package/dist/src/ui/public/style.css +3 -1
- package/dist/src/ui/server.js +54 -15
- package/docs/architecture.md +7 -5
- package/docs/mobile.md +59 -0
- package/docs/releases.md +18 -0
- package/docs/ui.md +15 -7
- package/package.json +4 -2
package/dist/src/ui/server.js
CHANGED
|
@@ -14,6 +14,7 @@ import { EvidenceError, readEvidence } from "./evidence.js";
|
|
|
14
14
|
import { buildPlanningPrompt, planFlow, PlannerError } from "../planner.js";
|
|
15
15
|
import { renderMarkdown } from "../report.js";
|
|
16
16
|
import { configSchema, idSchema, modelSchema, suiteSchema, targetSchema } from "../schema.js";
|
|
17
|
+
import { targetFingerprint } from "../targets.js";
|
|
17
18
|
import { CONFIG_FILE, sha256 } from "../workspace.js";
|
|
18
19
|
const MAX_BODY = 256 * 1024;
|
|
19
20
|
const MAX_FLOW = 128 * 1024;
|
|
@@ -28,7 +29,10 @@ const RUN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
|
28
29
|
const FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/;
|
|
29
30
|
const reportEnvelope = z.object({
|
|
30
31
|
schemaVersion: z.literal('flow-qa/report-1'), runId: z.string().regex(RUN_ID), suiteId: idSchema,
|
|
31
|
-
suiteHash: z.string(), target: z.
|
|
32
|
+
suiteHash: z.string(), target: z.union([
|
|
33
|
+
z.object({ name: z.string(), url: z.string() }),
|
|
34
|
+
z.object({ name: z.string(), kind: z.literal('mobile'), platform: z.enum(['ios', 'android']), bundleId: z.string(), device: z.string() }),
|
|
35
|
+
]),
|
|
32
36
|
startedAt: z.string(), finishedAt: z.string(), status: z.enum(['passed', 'failed', 'blocked', 'error', 'interrupted']),
|
|
33
37
|
cases: z.array(z.object({ id: z.string(), title: z.string(), status: z.enum(['passed', 'failed', 'flaky', 'blocked', 'skipped', 'error']),
|
|
34
38
|
expected: z.array(z.string()), attempts: z.array(z.unknown()), reason: z.string().optional() }).passthrough()),
|
|
@@ -66,7 +70,8 @@ const SCHEMA_FIELDS = new Set([
|
|
|
66
70
|
'suite', 'schemaVersion', 'id', 'title', 'source', 'file', 'sha256', 'requirements', 'cases',
|
|
67
71
|
'requirementIds', 'preconditions', 'questions', 'steps', 'type', 'path', 'instruction',
|
|
68
72
|
'locator', 'by', 'value', 'name', 'visible', 'expected', 'match', 'flow', 'role', 'url',
|
|
69
|
-
'description', 'baseURL', 'model', 'apiKeyEnv', 'clear', 'targetName', 'targetURL', 'suiteHash',
|
|
73
|
+
'description', 'baseURL', 'model', 'apiKeyEnv', 'clear', 'targetName', 'targetURL', 'targetFingerprint', 'suiteHash',
|
|
74
|
+
'kind', 'platform', 'bundleId', 'device', 'direction',
|
|
70
75
|
]);
|
|
71
76
|
const KNOWN_SCHEMA_MESSAGES = new Map([
|
|
72
77
|
['IDs must be unique.', 'IDs must be unique.'],
|
|
@@ -108,7 +113,8 @@ function plannerFailure(error) {
|
|
|
108
113
|
}
|
|
109
114
|
if (error.code === 'invalid-suite') {
|
|
110
115
|
const allowedMessages = new Set(['A JSON object response is required.', 'A runnable case needs an expected-result assertion.',
|
|
111
|
-
'Check requirement links, duplicate IDs, and assertions.', 'Check required values, types, and limits in the case schema.'
|
|
116
|
+
'Check requirement links, duplicate IDs, and assertions.', 'Check required values, types, and limits in the case schema.',
|
|
117
|
+
'The suite platform must match the selected planning platform.']);
|
|
112
118
|
const allowedPaths = new Set([...SCHEMA_FIELDS, 'response', 'suite', '?']);
|
|
113
119
|
const issues = error.issues?.slice(0, 8).map((issue) => ({
|
|
114
120
|
path: issue.path.split('.').slice(0, 8).every((part) => allowedPaths.has(part) || /^(?:0|[1-9][0-9]?)$/.test(part)) ? issue.path : 'suite',
|
|
@@ -439,7 +445,7 @@ export async function startUIServer(options) {
|
|
|
439
445
|
const settings = await config();
|
|
440
446
|
const history = await runs();
|
|
441
447
|
sendJSON(response, 200, { csrfToken: token,
|
|
442
|
-
targets: Object.entries(settings.targets).map(([name, target]) => ({ name, ...target })),
|
|
448
|
+
targets: Object.entries(settings.targets).map(([name, target]) => ({ name, ...target, targetFingerprint: targetFingerprint(target) })),
|
|
443
449
|
models: { planner: modelState(settings.planner), agent: modelState(settings.agent) },
|
|
444
450
|
runtime: { browserInstalled: await browserInstalled(), browserInstallCommand: BROWSER_INSTALL_COMMAND },
|
|
445
451
|
suites: await suites(), runs: history,
|
|
@@ -522,10 +528,10 @@ export async function startUIServer(options) {
|
|
|
522
528
|
throw new HTTPError(403, 'Invalid request token.');
|
|
523
529
|
const body = await bodyJSON(request);
|
|
524
530
|
if (url.pathname === '/api/prompt') {
|
|
525
|
-
const { flow } = parse(z.object({ flow: z.string().min(1) }).strict(), body);
|
|
531
|
+
const { flow, platform } = parse(z.object({ flow: z.string().min(1), platform: z.enum(['web', 'mobile']).optional() }).strict(), body);
|
|
526
532
|
if (Buffer.byteLength(flow) > MAX_FLOW)
|
|
527
533
|
throw new HTTPError(413, 'The flow is too large.');
|
|
528
|
-
sendJSON(response, 200, { prompt: buildPlanningPrompt(flow) });
|
|
534
|
+
sendJSON(response, 200, { prompt: buildPlanningPrompt(flow, platform) });
|
|
529
535
|
return;
|
|
530
536
|
}
|
|
531
537
|
if (url.pathname === '/api/stop') {
|
|
@@ -539,7 +545,7 @@ export async function startUIServer(options) {
|
|
|
539
545
|
}
|
|
540
546
|
if (url.pathname === '/api/plan') {
|
|
541
547
|
const result = await mutation(async () => {
|
|
542
|
-
const { flow } = parse(z.object({ flow: z.string().min(1) }).strict(), body);
|
|
548
|
+
const { flow, platform } = parse(z.object({ flow: z.string().min(1), platform: z.enum(['web', 'mobile']).optional() }).strict(), body);
|
|
543
549
|
if (Buffer.byteLength(flow) > MAX_FLOW)
|
|
544
550
|
throw new HTTPError(413, 'The flow is too large.');
|
|
545
551
|
const settings = await config();
|
|
@@ -560,7 +566,7 @@ export async function startUIServer(options) {
|
|
|
560
566
|
timeout.unref();
|
|
561
567
|
try {
|
|
562
568
|
const result = await Promise.race([
|
|
563
|
-
Promise.resolve().then(() => plan(flow, settings.planner, { signal: abort.signal })),
|
|
569
|
+
Promise.resolve().then(() => plan(flow, settings.planner, { signal: abort.signal, platform })),
|
|
564
570
|
aborted,
|
|
565
571
|
]);
|
|
566
572
|
if (abort.signal.aborted)
|
|
@@ -569,6 +575,8 @@ export async function startUIServer(options) {
|
|
|
569
575
|
if (!parsed.success)
|
|
570
576
|
throw new HTTPError(502, 'The model returned an invalid suite.', { category: 'invalid-suite' });
|
|
571
577
|
const planned = parsed.data;
|
|
578
|
+
if ((planned.platform ?? 'web') !== (platform ?? 'web'))
|
|
579
|
+
throw new HTTPError(502, 'The model returned cases for a different platform.', { category: 'invalid-suite' });
|
|
572
580
|
const { source: _modelSource, ...withoutSource } = planned;
|
|
573
581
|
const suite = suiteSchema.parse(withoutSource);
|
|
574
582
|
return { suite, script: compileSuite(suite) };
|
|
@@ -592,6 +600,25 @@ export async function startUIServer(options) {
|
|
|
592
600
|
sendJSON(response, 200, result);
|
|
593
601
|
return;
|
|
594
602
|
}
|
|
603
|
+
if (url.pathname === '/api/import') {
|
|
604
|
+
const result = await mutation(async () => {
|
|
605
|
+
const { suite: inputSuite, flow } = parse(z.object({ suite: suiteSchema, flow: z.string().min(1).optional() }).strict(), body);
|
|
606
|
+
if (flow !== undefined && Buffer.byteLength(flow) > MAX_FLOW)
|
|
607
|
+
throw new HTTPError(413, 'The flow is too large.');
|
|
608
|
+
sourceName(inputSuite);
|
|
609
|
+
if (inputSuite.source) {
|
|
610
|
+
if (flow === undefined)
|
|
611
|
+
invalid('Select the source Markdown together with the case JSON.');
|
|
612
|
+
if (sha256(flow) !== inputSuite.source.sha256)
|
|
613
|
+
throw new HTTPError(409, 'The selected Markdown does not match the case source.');
|
|
614
|
+
}
|
|
615
|
+
// Import prepares a review copy; saving later assigns its workspace source path.
|
|
616
|
+
const { source: _source, ...suite } = inputSuite;
|
|
617
|
+
return { suite, script: compileSuite(suite) };
|
|
618
|
+
});
|
|
619
|
+
sendJSON(response, 200, result);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
595
622
|
if (url.pathname === '/api/compile') {
|
|
596
623
|
const result = await mutation(async () => {
|
|
597
624
|
const { suite } = parse(z.object({ suite: suiteSchema }).strict(), body);
|
|
@@ -629,8 +656,11 @@ export async function startUIServer(options) {
|
|
|
629
656
|
}
|
|
630
657
|
if (url.pathname === '/api/targets') {
|
|
631
658
|
await mutation(async () => {
|
|
632
|
-
const { name,
|
|
633
|
-
|
|
659
|
+
const { name, ...input } = parse(z.union([
|
|
660
|
+
z.object({ name: idSchema, kind: z.literal('mobile'), platform: z.enum(['ios', 'android']), bundleId: z.string(), device: z.string(), description: z.string().optional() }).strict(),
|
|
661
|
+
z.object({ name: idSchema, kind: z.literal('web').optional(), url: z.string(), description: z.string().optional() }).strict(),
|
|
662
|
+
]), body);
|
|
663
|
+
const target = parse(targetSchema, input);
|
|
634
664
|
const settings = await config();
|
|
635
665
|
settings.targets[name] = target;
|
|
636
666
|
await updateConfig(settings);
|
|
@@ -658,18 +688,27 @@ export async function startUIServer(options) {
|
|
|
658
688
|
if (url.pathname === '/api/run') {
|
|
659
689
|
enterMutation();
|
|
660
690
|
try {
|
|
661
|
-
const { suite: file, suiteHash, targetName, targetURL } = parse(z.object({
|
|
662
|
-
suite: z.string(), suiteHash: z.string().regex(/^[a-f0-9]{64}$/), targetName: idSchema,
|
|
691
|
+
const { suite: file, suiteHash, targetName, targetURL, targetFingerprint: reviewedFingerprint } = parse(z.object({
|
|
692
|
+
suite: z.string(), suiteHash: z.string().regex(/^[a-f0-9]{64}$/), targetName: idSchema,
|
|
693
|
+
targetURL: z.string().optional(), targetFingerprint: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
663
694
|
}).strict(), body);
|
|
664
695
|
const selected = await getSuite(file, true);
|
|
665
696
|
if (selected.suiteHash !== suiteHash)
|
|
666
697
|
throw new HTTPError(409, 'The reviewed case file changed. Open it again to review.');
|
|
667
698
|
const { suite } = selected;
|
|
668
699
|
const settings = await config();
|
|
669
|
-
|
|
700
|
+
const target = settings.targets[targetName];
|
|
701
|
+
if (!target)
|
|
670
702
|
invalid('The selected target was not found.');
|
|
671
|
-
if (
|
|
672
|
-
throw new HTTPError(409, 'The selected target
|
|
703
|
+
if ((suite.platform ?? 'web') !== (target.kind === 'mobile' ? 'mobile' : 'web')) {
|
|
704
|
+
throw new HTTPError(409, 'The case platform and selected target do not match. Review them again.');
|
|
705
|
+
}
|
|
706
|
+
if (reviewedFingerprint !== undefined) {
|
|
707
|
+
if (targetFingerprint(target) !== reviewedFingerprint)
|
|
708
|
+
throw new HTTPError(409, 'The selected target changed. Review it again.');
|
|
709
|
+
}
|
|
710
|
+
else if (target.kind === 'mobile' || !targetURL || target.url !== targetURL) {
|
|
711
|
+
throw new HTTPError(409, 'The selected target changed. Review it again.');
|
|
673
712
|
}
|
|
674
713
|
const operationId = randomUUID();
|
|
675
714
|
const abort = new AbortController();
|
package/docs/architecture.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture and verification boundaries
|
|
2
2
|
|
|
3
|
-
The local CLI and loopback web UI share one workspace format, case schema, execution path, and report contract. Flow QA runs browser tests against a target URL
|
|
3
|
+
The local CLI and loopback web UI share one workspace format, case schema, execution path, and report contract. Flow QA runs browser tests against a target URL and native tests against a user-configured iOS or Android app/device. It does not need the target application's source code. Mobile integration is available from version 0.2.0; see the [mobile guide](mobile.md).
|
|
4
4
|
|
|
5
5
|
```mermaid
|
|
6
6
|
flowchart LR
|
|
@@ -8,7 +8,7 @@ flowchart LR
|
|
|
8
8
|
B --> C[JSON case suite]
|
|
9
9
|
C --> D[Schema and source-hash validation]
|
|
10
10
|
D --> E[Compile allowed steps to e2e code]
|
|
11
|
-
E --> F[tester-army/e2e
|
|
11
|
+
E --> F[tester-army/e2e web or mobile run]
|
|
12
12
|
F --> G[QA report with attempts and evidence]
|
|
13
13
|
H[CLI / local UI] --> B
|
|
14
14
|
H --> D
|
|
@@ -22,7 +22,9 @@ flowchart LR
|
|
|
22
22
|
|
|
23
23
|
**Only a defined vocabulary compiles.** The schema checks requirement references, assertions, step types, and inputs. It does not execute arbitrary code or shell commands from model output. Cases with unresolved `questions`, or steps that need an unavailable agent model, are blocked. `preconditions` describe required state; they are not an automatic setup contract.
|
|
24
24
|
|
|
25
|
-
**Source and evidence stay linked.** Planning records the source flow's SHA-256 hash in the suite. `validate`, `generate`, and `run` check it again and stop when the flow changed. Each execution writes a new directory with a suite snapshot, generated tests, raw engine output, and Markdown/JSON reports. Previous evidence is not reused as a current success. `compare` permits a direct comparison only for the same suite hash and target URL; it cannot establish that external data was unchanged.
|
|
25
|
+
**Source and evidence stay linked.** Planning records the source flow's SHA-256 hash in the suite. `validate`, `generate`, and `run` check it again and stop when the flow changed. Each execution writes a new directory with a suite snapshot, generated tests, raw engine output, and Markdown/JSON reports. Previous evidence is not reused as a current success. `compare` permits a direct comparison only for the same suite hash and target identity (web URL or native platform/app/device); it cannot establish that external data was unchanged.
|
|
26
|
+
|
|
27
|
+
**Targets can be configured after authoring.** A suite's optional platform distinguishes web from mobile. Omitted platform preserves legacy web suites and their hashes. Native app IDs and explicit device names belong to target settings, not generated code from the planner. UI execution checks a fingerprint of the reviewed target identity. Mobile runtime readiness is checked before device execution; configuration alone does not prove connectivity.
|
|
26
28
|
|
|
27
29
|
**Blocked and failed mean different things.** An unreachable app or unresolved question is blocked; an assertion mismatch is failed. A pass after retry has its own status. Engine output is validated rather than treating missing results as passes. The report records observations, not a root-cause verdict on the product.
|
|
28
30
|
|
|
@@ -31,8 +33,8 @@ flowchart LR
|
|
|
31
33
|
| Area | Flow QA provides | External implementation or prerequisite |
|
|
32
34
|
| --- | --- | --- |
|
|
33
35
|
| Planning | Prompt, model connection, JSON schema validation | User-selected OpenAI-compatible model |
|
|
34
|
-
| Execution | Step compilation, target connection, orchestration, cancellation | [tester-army/e2e](https://github.com/tester-army/e2e), Chromium |
|
|
36
|
+
| Execution | Step compilation, target connection, orchestration, cancellation | [tester-army/e2e](https://github.com/tester-army/e2e), Chromium for web or `@e2e-dev/mobile`/agent-device for native apps |
|
|
35
37
|
| Results | A fresh report per run, status interpretation, evidence links | Raw runs and evidence from the engine |
|
|
36
38
|
| UI | Local workflow screens and English/Korean language switch | [Hyeonse Design System](https://github.com/hyeonse-swing/hyeonse-design-system) tokens and preference utilities |
|
|
37
39
|
|
|
38
|
-
The UI binds only to `127.0.0.1` and serves approved report evidence, not arbitrary raw logs or HTML/SVG. Local reports may still contain data from the tested app and should be reviewed before sharing.
|
|
40
|
+
The UI binds only to `127.0.0.1` and serves approved report evidence, not arbitrary raw logs or HTML/SVG. Local reports may still contain data from the tested app and should be reviewed before sharing. It does not manage login sessions, cap model spending, schedule CI runs, or provision mobile SDKs, devices, and application builds.
|
package/docs/mobile.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# User-configured iOS and Android targets
|
|
2
|
+
|
|
3
|
+
Mobile targets require Flow QA `0.2.0` or later and Node.js 24.8 or later. The web demo remains a browser demo; it is not proof of a mobile run.
|
|
4
|
+
|
|
5
|
+
## Write cases now, connect an app later
|
|
6
|
+
|
|
7
|
+
Choose **Mobile** on the scenario screen, describe the flow, and generate or download a planning prompt. You do not need to register an app or device to author, validate, compile, or save cases. A mobile suite has `"platform": "mobile"`; existing suites without a platform remain web suites.
|
|
8
|
+
|
|
9
|
+
With the installed CLI:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install -g @ihyeon/flow-qa@0.2.1
|
|
13
|
+
flow-qa init ./my-qa
|
|
14
|
+
# Write your mobile flow in my-qa/flows/my-flow.md first.
|
|
15
|
+
flow-qa plan flows/my-flow.md --platform mobile --prompt-only --workspace ./my-qa
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use your chosen AI tool to produce JSON from the prompt, then review and save it under `my-qa/cases/`. Alternatively, configure a planner and omit `--prompt-only`. Neither option needs the target app's source code. Product behavior and accessible element names must still be supplied; unknown expectations remain questions.
|
|
19
|
+
|
|
20
|
+
## Register an app when ready
|
|
21
|
+
|
|
22
|
+
In **Connections**, select **iOS** or **Android**, then enter a target name, the installed app's bundle/package ID, and an explicit device name or ID. Register multiple targets to use one mobile suite on both platforms when their accessible labels and expected behavior agree. Use separate suites where the two apps behave differently.
|
|
23
|
+
|
|
24
|
+
CLI examples below contain placeholders; replace them with your own app and devices:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
flow-qa connect my-ios --platform ios --bundle-id com.example.app --device "QA iPhone" --workspace ./my-qa
|
|
28
|
+
flow-qa connect my-android --platform android --bundle-id com.example.app --device emulator-5554 --workspace ./my-qa
|
|
29
|
+
flow-qa run cases/my-flow.json --target my-ios --workspace ./my-qa
|
|
30
|
+
flow-qa run cases/my-flow.json --target my-android --workspace ./my-qa
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Saving a target only writes configuration. It does not contact a model, boot a device, install an app, or start a test. You can update a saved target by saving the same target name again. The UI checks the reviewed app, platform, and device before dispatching a run; changed settings require another review.
|
|
34
|
+
|
|
35
|
+
For an iOS simulator, prepare Xcode, an installed simulator runtime, a booted simulator, and a simulator build of the app installed on it. For Android, prepare the Android SDK and a booted emulator or an authorized connected device with the app installed. Physical iOS devices additionally need pairing, Developer Mode, and signing of the upstream automation runner. Follow the [mobile engine's setup instructions](https://e2e.tester.army/docs/mobile#physical-devices) for device-specific requirements.
|
|
36
|
+
|
|
37
|
+
Flow QA does not download SDKs, create or boot simulators, or install application builds. A missing or unavailable selected device blocks the run; a saved target is not evidence of a working device or app. For physical device IDs that the underlying engine resolves by name, the device name must be unique after case and whitespace normalization; ambiguous selection blocks the run. Chromium is only required for web targets.
|
|
38
|
+
|
|
39
|
+
## Native steps
|
|
40
|
+
|
|
41
|
+
| Step | Behavior |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `launchApp` | Launch the app registered in the selected target |
|
|
44
|
+
| `click` | Tap an accessible element |
|
|
45
|
+
| `fill` | Enter text in an accessible input |
|
|
46
|
+
| `back` | Use the engine's app back navigation |
|
|
47
|
+
| `swipe` | Swipe the viewport `up`, `down`, `left`, or `right` |
|
|
48
|
+
| `assertVisible`, `assertText` | Check accessible elements and their text |
|
|
49
|
+
| `act`, `assertAgent` | Use the separately configured agent model for an action or judgment |
|
|
50
|
+
|
|
51
|
+
The compiler launches the configured app at the start of each runnable case unless the first step is already `launchApp`. It does not clear app data or create login state. A launch is not a fresh account/session. The web-only `open` path and `assertUrl` steps are rejected in mobile suites, and native steps are rejected in web suites.
|
|
52
|
+
|
|
53
|
+
Native `testId` locators use iOS accessibility identifiers or Android resource IDs. Accessibility exposure depends on the app framework. Fixed steps need no model; natural-language steps require a compatible tool/image-capable agent. A WebView's exposed native controls may be usable, but this path does not provide a browser DOM session or prove JavaScript bridge behavior.
|
|
54
|
+
|
|
55
|
+
## Results and proof boundaries
|
|
56
|
+
|
|
57
|
+
Mobile reports record platform, app ID, and device rather than a fabricated URL. They use the same case statuses, attempt details, and approved screenshot/text evidence handling as web reports. Comparing different apps, platforms, or configured devices does not produce a direct regression verdict.
|
|
58
|
+
|
|
59
|
+
Configuration, compilation, mocked runtime tests, and browser UI tests establish the integration behavior. Successful iOS and Android execution still requires recorded results on the user's chosen devices and apps. Flow QA does not supply app-specific cases, accounts, build artifacts, or a device farm.
|
package/docs/releases.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
The alpha entries below are historical release records. They document earlier package versions and checks.
|
|
4
4
|
|
|
5
|
+
## 0.2.1: import case files from the UI
|
|
6
|
+
|
|
7
|
+
Adds a file picker for a TC JSON file and its optional source Markdown. Import checks the suite and source hash before replacing the current draft, preserves unresolved review questions, and creates a separate copy when saved. Source text retains its line endings and trailing whitespace. This does not import error-to-test provenance metadata; original bundles remain separate. See [case file import](ui.md#import-existing-case-files).
|
|
8
|
+
|
|
9
|
+
Locally verified on macOS with Node.js 24.14.1 on 2026-10-15: 92 tests, type checking, UI syntax and design-system audit, real Chromium import/save/retry checks in English and Korean, existing demo pass/failure/blocked classifications, and a fresh tarball install with three passing browser cases. No model calls or native-device runs were used for this patch.
|
|
10
|
+
|
|
11
|
+
- Package: [`@ihyeon/flow-qa@0.2.1`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.2.1)
|
|
12
|
+
- Release: [GitHub 0.2.1](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.2.1)
|
|
13
|
+
|
|
14
|
+
## 0.2.0: configurable mobile targets
|
|
15
|
+
|
|
16
|
+
Adds native iOS/Android target settings, mobile case authoring before device setup, platform-aware compilation and execution, and app/device identity in review and reports. Users register an installed app and a prepared device when ready; no product-specific app is bundled. Existing web settings and suites remain readable. Node.js 24.8 or later is required by the mobile runtime. Actual native-device success requires a user-selected app and device. See [mobile setup and verification limits](mobile.md).
|
|
17
|
+
|
|
18
|
+
Locally verified on macOS with Node.js 24.14.1 on 2026-10-12: 89 tests, type checking, UI syntax and design-system audit, package build and fresh tarball installation. Chromium smoke preserved web pass/failure/unavailable classifications and English/Korean UI behavior. The installed package can create a mobile prompt, validate and compile mobile cases before target setup, and save iOS/Android settings. Native generated code was type-checked against the installed SDK; device selection and blocking were tested with inventory fixtures. These checks did not execute a native app or call a model.
|
|
19
|
+
|
|
20
|
+
- Package: [`@ihyeon/flow-qa@0.2.0`](https://www.npmjs.com/package/@ihyeon/flow-qa/v/0.2.0)
|
|
21
|
+
- Release: [GitHub 0.2.0](https://github.com/hyeonse-swing/flow-qa/releases/tag/v0.2.0)
|
|
22
|
+
|
|
5
23
|
## Automated npm and GitHub releases
|
|
6
24
|
|
|
7
25
|
A push to `main` publishes only when `package.json` changes to a new plain `X.Y.Z` version. Pull requests and pushes without a version change run verification without publishing. The package and lockfile versions must agree; the version must increase, and registry errors stop the release. Rerunning a successful publication skips the existing immutable npm version.
|
package/docs/ui.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Flow QA local UI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Describe a user flow, review cases, and configure a target when ready to run. Web targets use a URL; native targets use an iOS/Android app ID and an explicit device. Mobile targets require version `0.2.0` or later. The local example is a to-do web app; the UI does not bundle an organization's product list, internal URLs, accounts, model catalog, work cases, or run history.
|
|
4
4
|
|
|
5
5
|
## Start
|
|
6
6
|
|
|
7
|
-
Node.js 24 or later is required. Install Flow QA and start the
|
|
7
|
+
Node.js 24.8 or later is required. Install Flow QA and start the web demo:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install -g @ihyeon/flow-qa@0.1
|
|
10
|
+
npm install -g @ihyeon/flow-qa@0.2.1
|
|
11
11
|
flow-qa browser install
|
|
12
12
|
flow-qa ui --demo
|
|
13
13
|
```
|
|
@@ -28,20 +28,28 @@ The UI starts in **English**, regardless of browser language. Switch to **한국
|
|
|
28
28
|
|
|
29
29
|
## Workflow
|
|
30
30
|
|
|
31
|
-
1. **
|
|
32
|
-
2. **
|
|
31
|
+
1. **Scenario:** Choose Web or Mobile and describe the initial state, actions, screen elements, and expected results. No target is required yet. If a planner is ready, ask it to create cases. Without a model, download the planning prompt and paste a JSON response from an AI you already use.
|
|
32
|
+
2. **Connections:** When ready, register a target name and web URL, or choose iOS/Android and enter an installed app's bundle/package ID and device. Planner and execution agent models have separate settings. Saving settings does not start an app or model request.
|
|
33
33
|
3. **Review and save cases:** Expand each case to read preconditions, actions, expected results, and unresolved questions. Editing the JSON keeps the linked flow; if that source flow changes, design the cases again. Planning and running are separate actions. Editing inputs or connection settings alone does not call a model.
|
|
34
|
-
4. **Run QA:** Review the saved case file, select a target, and check the review confirmation before running. If the file
|
|
34
|
+
4. **Run QA:** Review the saved case file, select a matching web/mobile target, and check the review confirmation before running. If the file or target settings change after review, reload and review them again. Fixed steps need no agent model. `act` and `assertAgent` require a compatible agent; missing models and unresolved questions are reported as blocked cases.
|
|
35
35
|
5. **Case library and Run history:** Save cases as new files and load them again. Open run details to see each case, attempt, and evidence; download the Markdown or JSON report.
|
|
36
36
|
|
|
37
37
|
The top bar shows planning, running, and stopping states, plus a cancel button. A late planning response cannot change the draft after cancellation. The UI does not estimate progress inside an individual browser action. If the server disconnects, reconnect; runs started in another tab also appear. Refreshing settings rereads targets and models, so save edits first.
|
|
38
38
|
|
|
39
39
|
For each attempt, the report can open or download an approved screenshot or text evidence item. It serves only evidence linked to the report and marked as redacted or not needing redaction. It does not serve arbitrary files, raw run logs, HTML, or SVG. Markdown and JSON downloads summarize results but do not include evidence files. Keep the original run directory structure if you need relative evidence links in an offline Markdown report.
|
|
40
40
|
|
|
41
|
-
**Connections** shows whether Chromium is installed. A model key marked ready means only that the named environment variable exists; it does not prove authentication or model compatibility. Case review shows runnable and pre-blocked counts. Login and target reachability are established only by an actual run.
|
|
41
|
+
**Connections** shows whether Chromium is installed for web targets. Mobile execution requires a prepared device and installed app; Chromium readiness does not determine mobile readiness. See [mobile setup](mobile.md). A model key marked ready means only that the named environment variable exists; it does not prove authentication or model compatibility. Case review shows runnable and pre-blocked counts. Login and target reachability are established only by an actual run.
|
|
42
42
|
|
|
43
43
|
To enter fresh JSON after changing a scenario, open the JSON editor in case review and choose **New JSON draft**. Existing saved files remain; validate and save the new JSON before reviewing it for a run.
|
|
44
44
|
|
|
45
|
+
### Import existing case files
|
|
46
|
+
|
|
47
|
+
From version `0.2.1`, select a `flow-qa/suite-1` JSON file and, when it declares a source, its Markdown file in case review, then choose **Import case files**. The two file controls let you select files from different folders. For an error-to-test export, select `cases/error-to-test.json` and `flows/error-to-test.md`. A JSON file without a source can be imported alone or with a Markdown file.
|
|
48
|
+
|
|
49
|
+
Import validates the suite and checks the selected Markdown against its declared SHA-256 before replacing the draft. Cancelling or selecting invalid files preserves the current draft. The Markdown text, including line endings and trailing whitespace, is retained for saving. Markdown is limited to 128 KiB; the combined JSON request is limited to 256 KiB.
|
|
50
|
+
|
|
51
|
+
This creates an unsaved review copy. Choose **Save cases separately**, review the cases, and confirm review before running. Unresolved questions remain blocked. Saving assigns a new workspace source path and hash; it does not overwrite the input files or import `provenance.json`. If you need error-to-test `verify` / `link-run` provenance, keep the original bundle and use its original suite instead of this saved copy.
|
|
52
|
+
|
|
45
53
|
## Display and language
|
|
46
54
|
|
|
47
55
|
The UI uses role tokens and preference utilities from `@hyeonse/design-system`, pinned to GitHub commit `38978c6a86f422fc0e63e919f600bc2dd3451e5c`. Flow QA implements its own workflow layout, buttons, and fields with those tokens.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ihyeon/flow-qa",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Hyeonse Im",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"description": "Turn user flows into repeatable tests and evidence-backed QA reports.",
|
|
39
39
|
"type": "module",
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=24"
|
|
41
|
+
"node": ">=24.8.0"
|
|
42
42
|
},
|
|
43
43
|
"bin": {
|
|
44
44
|
"flow-qa": "./dist/bin/flow-qa.mjs"
|
|
@@ -61,8 +61,10 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@ai-sdk/openai-compatible": "3.0.53",
|
|
64
|
+
"@e2e-dev/mobile": "0.11.0",
|
|
64
65
|
"@e2e-dev/web": "0.11.1",
|
|
65
66
|
"@hyeonse/design-system": "https://codeload.github.com/hyeonse-swing/hyeonse-design-system/tar.gz/38978c6a86f422fc0e63e919f600bc2dd3451e5c",
|
|
67
|
+
"agent-device": "0.21.22",
|
|
66
68
|
"ai": "7.0.107",
|
|
67
69
|
"e2e": "0.16.0",
|
|
68
70
|
"playwright": "1.63.0",
|