From d356a5cb1ba35e2e39c76c0db3e923f833e1030e Mon Sep 17 00:00:00 2001 From: xywang68 Date: Tue, 22 Sep 2026 13:24:41 -0700 Subject: [PATCH 1/4] docs: finish the vocabulary migration; migrate the last v1 emitter in the framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A staleness audit (grepping every doc for v1 flag names, the old command name and stale counts) found what my earlier passes had missed: README.md - The seam section still documented the v1 arguments as current: a table of --ocrPath/--ocrSimilarity/--ocrMaxSim/--ocrWaitTime/--ocrMaxCount/--ocrAction/ --ocrDetail/--ocrPSM/--ocrOEM, and examples driving findTargetImage. Replaced with the canonical table and examples, pointing at docs/CONTRACT.md as the source of truth so the two can no longer drift. - The illustrative run output still showed 'findTargetImage --imagePath=...'; it now shows what the suite actually prints. - The feature table and the coverage-honesty notes still named --textHint, --maxSim, --imageAction, --imageMaxCount, --ocrPath, --ocrSimilarity and --ocrDetail. - The section intro claimed the interface was findTargetImage; it is autobdd, with findTargetImage as the deprecated alias. The duplicated 'Basic usage' block is gone (subsumed by 'The front door' + 'Examples'). docs/CONTRACT.md - The v1 argument table was left dangling under a 'Raw arguments' heading, which read as if it were current. Retitled 'Raw argument reference (v1 - deprecated)' with a pointer to the §2b mapping, and the two prose mentions of --imageAction/--ocrDetail and the 'center' row now use the canonical names. seam + suite - The help text's known-gap line and the suite's user-facing known-gap line said --ocrDetail=word; they now say --box, which is what the reader would type. - The help feature now asserts the canonical flag appears AND that the deprecated v1 names are listed, so the deprecation table cannot silently disappear. framework/libs/screen_session.js - This was the last v1 emitter: it still built 'findTargetImage --imagePath=... --imageAction=...', so after the rename every framework call would warn on stderr seven times. Now emits the front door with the canonical flags. textHint was a REGEX and --match-text is literal by default, so --match-regex is passed to preserve the existing semantics exactly, twice documented in the code. Verified: the generated framework command matches with 'clicked' dispatched and an EMPTY stderr (no deprecation warnings); the base suite runs 131 passed, 0 failed. The framework e2e suite itself was not run - it needs the L2 image with Chrome - so that consumer is verified at the command-string level plus the engine's legacy/v2 coverage. No v1 flag names remain in any documentation except the deprecation tables and the intentional historical notes in CHANGELOG.md. --- README.md | 154 ++++++------------ docs/CONTRACT.md | 8 +- framework/libs/screen_session.js | 28 +++- seam/bin/autobdd | 2 +- seam/src/findTargetImage.js | 2 +- .../autobdd-base-test/base-test/features.sh | 7 +- 6 files changed, 85 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 05ba649..c00c116 100644 --- a/README.md +++ b/README.md @@ -39,20 +39,13 @@ The product is published as two tags that are also two **layers** of one stack: `xyteam/autobdd:` is a **deprecated alias** of `xyteam/autobdd-framework:`. -## The public interface of the base image (the frozen seam) +## The public interface of the base image -The base image exposes a single command‑line program, **`findTargetImage`**, which is the -frozen public contract described in [`docs/CONTRACT.md`](docs/CONTRACT.md). -All communication with the base image happens through this program; it returns a JSON -object (or array) on **stdout**. - -### Basic usage (image matching) - -```bash -autobdd find-target --match-image= \ - --min-score=0.8 --max-score=1.0 \ - --wait=5s --click --limit=1 -``` +Everything you can do with the base image is reached through **`autobdd`**, the front door +described in [`docs/CONTRACT.md`](docs/CONTRACT.md). It writes a JSON object (or array) to +**stdout** on a single line, and the verbs are verb-object so a call reads as an instruction. +`findTargetImage` — the v1 name — still works as a **deprecated alias**, and the engine +behind both lives at `/opt/autobdd/seam/src/`, out of `PATH`. ### The front door @@ -95,85 +88,46 @@ These are answered **before the JVM and native engine start** — measured at ~4 was produced — **including `notFound`, which is an answer, not a fault** (branch on `.[0].status`) — and `2` for a usage error. Unknown arguments warn on stderr and are otherwise ignored, because the argument surface is additive; an *unusable value* (a -non‑numeric threshold, an unknown `--imageAction`) exits `2` instead of silently behaving +non‑numeric threshold, an unknown `--box` level) exits `2` instead of silently behaving like the default. -### Opt‑in OCR mode (new) - -All OCR‑related arguments are **optional**; if none of them are supplied the seam -behaves exactly as described above (pure image matching). -When `--ocrPath` is supplied the seam runs OCR instead of image matching. - -| Argument | Type | Default | Meaning | -|----------|------|---------|---------| -| `--ocrPath ` | string | *(required for OCR mode)* | Text to search for via OCR. | -| `--ocrSimilarity ` | float | `0.8` | Minimum OCR confidence (0‑1) to treat as a match. | -| `--ocrMaxSim ` | float | `1.0` | Maximum OCR confidence (upper bound). | -| `--ocrWaitTime ` | integer | `1000` | How long (ms) to wait for the OCR text to appear (polls until found or timeout). | -| `--ocrMaxCount ` | integer | `1` | Maximum number of OCR matches to return. | -| `--ocrAction ` | string | `none` | Action to perform on each OCR match: `none`, `click`, `doubleClick`, `rightClick`, `hover`, `hoverClick`. | -| `--ocrDetail ` | string | `none` | Include the OCR bounding box in the result. `line` and `word` both emit the matched text region: a tight box when the engine exposes one for the query, otherwise the searched region's rectangle. `none` omits `ocrDetails` entirely (backward‑compatible). | -| `--ocrPSM ` | integer | `7` | Tesseract Page Segmentation Mode (passed through to Oculix). | -| `--ocrOEM ` | integer | `3` | Tesseract OCR Engine Mode (passed through to Oculix). | - -### Output format (backward‑compatible) - -The seam always returns a JSON array (or a single `{status:'notFound'}` object). -Each result object contains the original fields: - -```json -{ - "name": "", // matched text (or template name) - "score": , // OCR confidence (0‑1) or image similarity - "text": [, ...], // OCR lines (full text split by '\n') - "location": { "x":, "y":, "width":, "height": }, - "dimension": { "width":, "height": }, - "center": { "x":, "y": }, - "clicked": { "x":, "y": } | null, // set only for clicking actions (click/doubleClick/rightClick/hoverClick) - "ocrDetails": [ // <-- ONLY present when --ocrDetail ≠ none - { - "text": "", // the OCR word or line - "x":, "y":, // top‑left corner - "width":, "height":, // size in pixels - "confidence": // OCR confidence for this token (0‑1) - }, - ... - ] -} -``` - -If `--ocrDetail=none` (the default) the `ocrDetails` field may be omitted or set to -an empty array – existing parsers that ignore this field see no change. +### Arguments -### Example usages +The full, authoritative list lives in **[`docs/CONTRACT.md`](docs/CONTRACT.md) §2** (canonical) +and **§2b** (the deprecated v1 names, still accepted and translated). In short: -* **Detect a word and get its bounding box (no action)** - - ```bash - findTargetImage --ocrPath="SUBMIT" --ocrDetail=word - ``` - -* **Click the first occurrence of a word** - - ```bash - findTargetImage --ocrPath="SUBMIT" --ocrAction=click --ocrDetail=word - ``` - -* **Double‑click the second occurrence of a phrase** - - ```bash - findTargetImage --ocrPath="END TEST" --ocrAction=doubleClick \ - --ocrDetail=word --ocrMaxCount=2 - ``` - -* **Hover over a line of text (no click)** +| Argument | Default | Meaning | +|---|---|---| +| `--match-image=` | — | the target picture; `Screen` reads the whole screen. Required unless `--match-text` is given. | +| `--match-text=` | — | the target text. With `--match-image` it gates the matched region; alone it searches the screen. | +| `--match-regex` | off | treat `--match-text` as a regex (default: literal, case‑insensitive) | +| `--min-score` / `--max-score` | 0.8 / 1 | score floor / ceiling (mirrors the JSON `score` field) | +| `--wait=` | 1s | wait for the target — `5s`, `800ms`; a bare number means seconds | +| `--limit=` | 1 | at most n matches | +| `--flash=` | 1s | on‑screen match flash; `0s` disables the pause | +| `--click` `--double-click` `--right-click` `--hover` | off | actions, composable — `--hover --click` is hover‑then‑click | +| `--box[=]` | off | include the matched box | +| `--psm=` / `--oem=` | 7 / 3 | Tesseract knobs | + +Presence defines the mode, so there is nothing to remember: `--match-image` alone, +`--match-text` alone, or both (a picture gated on its region's text). + +### Examples - ```bash - findTargetImage --ocrPath="Status:" --ocrAction=hover --ocrDetail=line - ``` +```bash +autobdd find-target --match-image=logo.png # locate a picture +autobdd find-target --match-text="SUBMIT" --box # find text, report its box +autobdd find-target --match-text="SUBMIT" --click # find text, click it +autobdd find-target --match-image=card.png --match-text=Total # gate a picture on its text +autobdd find-target --match-image=tile.png --limit=2 --wait=5s # several matches, wait +autobdd find-target --match-image=logo.png --hover --click # hover, then click +autobdd read-text # read the whole screen +``` -* **Combine image matching with OCR gating (textHint)** – unchanged from the original - seam; see `docs/CONTRACT.md` for details. +**Deprecated, still working:** every v1 name (`--imagePath`, `--ocrPath`, `--textHint`, +`--imageSimilarity`, `--maxSim`, `--imageWaitTime`, `--imageAction`, `--ocrDetail`, …) is +translated and warns on **stderr only**, so a v1 consumer keeps working untouched — see +`docs/CONTRACT.md` §2b. ## Under the hood @@ -235,18 +189,18 @@ AutoBDD_Ver= make docker-run jobs="base-test" # ~3.5 min; ends "ALL FEATUR ▸ image-similarity — accept a weak match at a low floor and reject it at a high one repro: base-test/one.sh image-similarity - run: findTargetImage --imagePath=/tmp/base-test.VGxClH/hello_blur.png \ - --imageSimilarity=0.5 --flash=0 [rc=0] + run: /usr/local/libexec/autobdd/find-target \ + --match-image=/tmp/base-test.VGxClH/hello_blur.png --min-score=0.5 --flash=0s [rc=0] ✓ a low floor accepts the blurred template = hello_blur.png - run: findTargetImage --imagePath=/tmp/base-test.VGxClH/hello_blur.png \ - --imageSimilarity=0.99 --flash=0 [rc=0] + run: /usr/local/libexec/autobdd/find-target \ + --match-image=/tmp/base-test.VGxClH/hello_blur.png --min-score=0.99 --flash=0s [rc=0] ✓ a high floor rejects it = notFound ▸ flash — hold the on-screen match flash for the requested time repro: base-test/one.sh flash timed without the fixture re-show (NOSHOW=1) - run: findTargetImage --imagePath=/tmp/.../hello.png [rc=0] - run: findTargetImage --imagePath=/tmp/.../hello.png --flash=0 [rc=0] + run: /usr/local/libexec/autobdd/find-target --match-image=/tmp/.../hello.png [rc=0] + run: /usr/local/libexec/autobdd/find-target --match-image=/tmp/.../hello.png --flash=0s [rc=0] measured: default 2241 ms vs --flash=0 1338 ms -> delta 903 ms ✓ the default flash pauses for at least 0.5 s (0 ms would mean the flash is not applied) = 903 (>= 500) ✓ the flash pause is bounded = 903 ms (<= 2500) @@ -284,10 +238,10 @@ Each feature is one line to reproduce inside the image: `base-test/one.sh expects …` on stderr and exit **2**. Silently behaving like the default would look like a successful call to an automated caller. diff --git a/framework/libs/screen_session.js b/framework/libs/screen_session.js index 4ba783b..9049571 100644 --- a/framework/libs/screen_session.js +++ b/framework/libs/screen_session.js @@ -17,14 +17,26 @@ module.exports = { var outputBuffer; var outputString; var returnVal; - var runCommand = 'findTargetImage' - + ' --imagePath=' + imagePath - + ' --imageSimilarity=' + imageSimilarity - + ' --maxSim=' + maxSim - + ' --textHint="' + textHint + '"' - + ' --imageWaitTime=' + imageWaitTime - + ' --imageAction=' + imageAction - + ' --imageMaxCount=' + imageMaxCount; + // Canonical v2 vocabulary via the front door. This was the last v1 emitter: the v1 + // names still work (they are translated) but would warn on stderr on every call. + // * --min-score / --max-score replace --imageSimilarity / --maxSim + // * --imageWaitTime was SECONDS, so --wait carries the unit + // * the action enum becomes composable flags (--hover --click) + // * textHint was a REGEX, so --match-regex is passed to keep its semantics; the v2 + // default for --match-text is a literal, which callers can opt into by dropping it + const actionFlags = { + none: '', click: '--click', single: '--click', + hover: '--hover', hoverClick: '--hover --click', + doubleClick: '--double-click', rightClick: '--right-click' + }[imageAction] || ''; + var runCommand = 'autobdd find-target' + + ' --match-image=' + imagePath + + ' --min-score=' + imageSimilarity + + ' --max-score=' + maxSim + + (textHint ? ' --match-text="' + textHint + '" --match-regex' : '') + + ' --wait=' + imageWaitTime + 's' + + (actionFlags ? ' ' + actionFlags : '') + + ' --limit=' + imageMaxCount; try { // display run command for debugging console.log(runCommand); diff --git a/seam/bin/autobdd b/seam/bin/autobdd index 5facd44..dc2837a 100755 --- a/seam/bin/autobdd +++ b/seam/bin/autobdd @@ -59,7 +59,7 @@ case "$1" in ;; read-text) shift - # A screen read is the engine's whole-screen mode. An explicit --imagePath from the + # A screen read is the engine's whole-screen mode. An explicit --match-image from the # caller wins, because the engine parses the last occurrence of a repeated flag. exec "$ENGINE" --match-image=Screen "$@" ;; diff --git a/seam/src/findTargetImage.js b/seam/src/findTargetImage.js index 6d85594..6be9791 100755 --- a/seam/src/findTargetImage.js +++ b/seam/src/findTargetImage.js @@ -96,7 +96,7 @@ KNOWN GAPS --min-score is applied to image matches; the OCR path exposes no per-match confidence to filter on, so it is accepted but not applied for text targets. --box reports the matched region, not one entry per token. - --ocrDetail=word reports the matched region, not one entry per token. + --box reports the matched region, not one entry per token. DEPRECATED (translated, with a warning on stderr) ${LEGACY.map(([o, n]) => ` ${pad(o, 26)} -> ${n}`).join('\n')} diff --git a/test-projects/autobdd-base-test/base-test/features.sh b/test-projects/autobdd-base-test/base-test/features.sh index 55d31ee..771d0c9 100755 --- a/test-projects/autobdd-base-test/base-test/features.sh +++ b/test-projects/autobdd-base-test/base-test/features.sh @@ -335,12 +335,12 @@ feat_ocr_similarity(){ check_eq "the floor argument is accepted and detection still works" "$(_jq "$JSON" '.[0].name')" "AUTOTEST OCR" JSON="$(target --match-text="NO SUCH TEXT HERE")" check_eq "absent text is rejected" "$(_jq "$JSON" '.[0].status')" "notFound" - # Deliberately NOT asserted: that raising --ocrSimilarity rejects a text whose + # Deliberately NOT asserted: that raising --min-score rejects a text whose # confidence is below it. Measured: with the text on screen, --min-score=0.99 # still returns a match, because this build's OCR path has no per-match confidence to # filter on (the image floor IS applied: see image-similarity). Asserting rejection # here would pass only when the screen happens to be blank — a false green. - printf ' \033[33mknown-gap: --ocrSimilarity is accepted but not applied by this build; only the image floor filters\033[0m\n' >&2 + printf ' \033[33mknown-gap: --min-score is applied to image matches but not to text targets by this build\033[0m\n' >&2 } feat_ocr_wait(){ feature ocr-wait @@ -420,7 +420,8 @@ feat_help(){ printf ' \033[2mrun: findTargetImage --help [rc=%s, %s ms]\033[0m\n' "$rc" "$ms" >&2 check_eq "exit status is 0" "$rc" "0" check_has "prints the flows" "$out" "FLOWS" - check_has "prints the flags" "$out" "--imagePath" + check_has "prints the canonical flags" "$out" "--match-image" + check_has "lists the deprecated v1 names" "$out" "--imagePath" # A real result line is always `target_result: [` + JSON; the help text merely names # the marker, so match the payload, not the documentation. check_eq "emits no result payload" "$(printf '%s' "$out" | grep -c 'target_result: \[')" "0" From 485c1b90883b38af5df4eeab75b21fb989cde46b Mon Sep 17 00:00:00 2001 From: xywang68 Date: Tue, 22 Sep 2026 14:06:20 -0700 Subject: [PATCH 2/4] release: cut 4.0.0 as the base line; v3.0.0 stays frozen The product splits into two lines. v3.0.0 is untouched - tag, release and image stay exactly as shipped, as the last release of the FRAMEWORK line (base + Chrome + WebdriverIO + Cucumber, published as xyteam/autobdd-framework:3.0.0 and the xyteam/autobdd alias). 4.0.0 is cut from it and narrows the product to the substrate: a docker-runnable GUI (Xvfb + openbox + x11vnc) with the Oculix screen engine behind one CLI, so higher-level tools - a framework, a script, an agent - drive it. No browser, no runner, no BDD layer. - .docker/autobdd-base.dockerfile: new ARG AUTOBDD_VERSION=4.0.0, recorded as version= in /etc/autobdd-versions. The framework image already selects its base with the same arg (FROM xyteam/autobdd-base:${AUTOBDD_VERSION}), so the two lines stay coherent. Also fixed a stale stamp field: oculix= pointed at /opt/autobdd/third_party/xysikulixapi/lib, which stopped existing when the seam became in-tree, so the field was silently empty. - seam: --version now reports the recorded version alongside the build stamp, the Oculix jar and Node, and names the product ('AutoBDD base image'). - package.json + package-lock.json: 3.0.0 -> 4.0.0. - README: repositions the product as the 4.0.0 base line with the goal stated plainly (a GUI you drive from higher-level tools), documents the two lines and which image belongs to which, and adds a v4.0.0 row to the version table with v3.0.0 marked frozen. - CHANGELOG: a v4.0.0 section describing the re-scope and what it meant in practice (one interface, a readable vocabulary, discoverability without starting the engine, self-contained natives, a conformance suite that doubles as the spec). - suite: gates on the release - provenance asserts version=4.0.0 in /etc/autobdd-versions and the version feature asserts --version reports it. Group I's discovery features now invoke the surface under test (TARGET_BIN) rather than a hardcoded findTargetImage, so the log matches the banner's stated surface. Verified: autobdd --version -> 'version: 4.0.0'; make base-test -> 133 passed, 0 failed, ALL FEATURES OK; one.sh I -> 18 passed. --- .docker/autobdd-base.dockerfile | 7 +++- CHANGELOG.md | 34 +++++++++++++++++++ README.md | 34 +++++++++++++------ package-lock.json | 4 +-- package.json | 2 +- seam/src/findTargetImage.js | 13 ++++--- .../autobdd-base-test/base-test/features.sh | 21 +++++++----- 7 files changed, 87 insertions(+), 28 deletions(-) diff --git a/.docker/autobdd-base.dockerfile b/.docker/autobdd-base.dockerfile index c94d37c..49a6ae9 100644 --- a/.docker/autobdd-base.dockerfile +++ b/.docker/autobdd-base.dockerfile @@ -72,6 +72,10 @@ RUN apt-get update -y && \ # --------------------------------------------------------------------------- # Exact + checksum-verified (NFR-P3/P5): the official tarball, not a floating apt repo. # Node 20 reached EOL 2026-04-30; 24 is the active LTS (EOL 2028-04-30). +# The product version this image is built as. It is recorded in /etc/autobdd-versions and +# reported by `autobdd --version`; the framework image selects its base with the same arg +# (FROM xyteam/autobdd-base:${AUTOBDD_VERSION}). +ARG AUTOBDD_VERSION=4.0.0 ARG NODE_VERSION=24.21.0 ARG NODE_SHA256=fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6 RUN curl -fsSL -o /tmp/node.tar.xz "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" && \ @@ -128,12 +132,13 @@ EXPOSE 22 # Record the resolved versions this base was built with (NFR-P4). The framework appends # its own (Chrome/chromedriver) lines to the same file. RUN { echo "# autobdd-base"; \ + echo "version=${AUTOBDD_VERSION}"; \ echo "os=$(. /etc/os-release; echo $PRETTY_NAME)"; \ echo "ubuntu_digest=sha256:224a1869083a311ef3f13648a154ba79832fbef6364d31493642ca03082da254"; \ echo "java=$(java -version 2>&1 | head -1)"; \ echo "node=$(node -v)"; \ echo "python=$(python3 --version 2>&1)"; \ - echo "oculix=$(ls /opt/autobdd/third_party/xysikulixapi/lib 2>/dev/null | grep -E '\.jar$' | head -1)"; \ + echo "oculix=$(ls /opt/autobdd/seam/lib 2>/dev/null | grep -E '\.jar$' | head -1)"; \ for p in openjdk-17-jdk xvfb openbox x11vnc arc-theme lxde imagemagick ffmpeg \ xdotool wmctrl xdg-utils python3 python3-pip supervisor tini \ fonts-wqy-microhei fonts-wqy-zenhei; do \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 05b1b51..af4d7e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -159,6 +159,40 @@ in `findTargetImage`, all fixed here: The v1 release cuts when Phase B is green and publishes the two tags. +## v4.0.0 + +**A re-scope, not a re-baseline: the base becomes the product.** `v3.0.0` is untouched — +tag, release and image stay exactly as shipped, as the last release of the framework line. +`v4.0.0` is cut from it and narrows the product to the **substrate**: a docker-runnable GUI +(Xvfb + openbox + x11vnc) with the Oculix screen engine behind one CLI, so that higher-level +tools — a framework, a script, an agent — drive it. No browser, no test runner, no BDD layer. + +What that meant in practice, all in this release: + +* **One interface.** `autobdd ` replaced the bare `findTargetImage` binary: + `autobdd find-target` locates a target (a picture, or text) and optionally acts on it; + `autobdd read-text` reads the screen. The v1 name remains a deprecated alias. +* **A vocabulary that reads as an instruction.** `--match-image` / `--match-text` name the + target; `--min-score`, `--max-score`, `--wait=5s`, `--limit`, `--box`, `--psm`, `--oem` + state everything once for both modalities; actions are composable flags (`--hover --click`). + Every v1 flag is translated, with a stderr-only warning. +* **Discoverability.** `--help`/`--list`/`--version` answer in ~40 ms **without starting the + engine** (measuring the old behaviour: 3079 ms and a whole-screen scan, with zero usage + strings in the binary); unusable values exit 2 instead of silently defaulting. +* **A self-contained image.** The Oculix natives bundled in the engine JAR are extracted at + build time into `/opt/oculix-natives` and registered with `ldconfig`: no runtime + extraction, no writable `/tmp` or `/root` needed. +* **A conformance suite that is also the spec.** 44 features / 131 checks, each independently + reproducible (`base-test/one.sh `), each printing the exact command it ran and the + value it observed. Writing it found and fixed six real defects in the engine, and five in + the suite's own narration. +* **The image reports what it is.** `version=` is recorded in `/etc/autobdd-versions` and + printed by `autobdd --version`; the suite gates on it. + +The framework image (`xyteam/autobdd-framework`, and the `xyteam/autobdd` alias) remains on +the 3.0.0 line; it pins its base via `AUTOBDD_VERSION`, so re-baselining it onto 4.0.0 is a +later, separate change that must pass the framework e2e suite. + ## v3.0.0 The runtime re-baseline — the continuation of v2.4.0. diff --git a/README.md b/README.md index c00c116..ef5d056 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,31 @@ # AutoBDD -**AutoBDD** — a BDD automation framework that drives the **screen** (image matching + -OCR), not just the DOM. `package.json` `3.0.0`. +**AutoBDD 4.0.0 — the base line.** A **docker-runnable GUI** (X display + window manager + +VNC) with a screen engine inside, exposed as one CLI so that **higher-level tools drive it**: +your framework, your own scripts, or an agent. It finds things on screen — a picture, or +text — and can act on them. `package.json` `4.0.0`. + +The job of this release is deliberately narrow: be the **substrate**. It does not ship a +browser, a test runner or a BDD layer. Everything above that plugs in through the +`autobdd` interface described in [`docs/CONTRACT.md`](docs/CONTRACT.md). + +> **3.0.0 is unchanged and stays published.** That tag, release and image +> (`xyteam/autobdd-framework:3.0.0`, plus the `xyteam/autobdd` alias) remain exactly as +> shipped — the last release of the **framework** line, which bundled the base with Chrome, +> WebdriverIO and Cucumber. 4.0.0 is a new line cut from it, focused on the base. ## What this repository is (and isn't) -This repo is the **framework source and the build source of the AutoBDD docker -images**. You do **not** need to clone it to use AutoBDD. +This repo is the **build source of the AutoBDD docker images**. You do **not** need to +clone it to use them. * The images are built here and **published to Docker Hub**: - **`xyteam/autobdd-base`** and **`xyteam/autobdd-framework`**. **`xyteam/autobdd`** - is kept as a **deprecated alias** of `autobdd-framework` (same image, two tags), - so existing consumers keep working. -* The image is **Ubuntu 24.04** based. + **`xyteam/autobdd-base`** — the 4.0.0 line, and what this release is about — + and **`xyteam/autobdd-framework`**, the 3.0.0 line (base + browser + BDD). The + framework image still builds from this repo and pins its base with + `AUTOBDD_VERSION`; re-baselining it onto the 4.0.0 base is future work. + **`xyteam/autobdd`** is a **deprecated alias** of `autobdd-framework`. +* The images are **Ubuntu 24.04** based. * **Test repositories pull and run the image directly** — e.g. [AutoBDD-example](https://github.com/xyteam/AutoBDD-example) runs its suite against `xyteam/autobdd:` with no framework clone required. @@ -22,7 +35,7 @@ images**. You do **not** need to clone it to use AutoBDD. # a test repo selects the published image via AutoBDD_Ver and runs its own compose; # no AutoBDD framework clone is required cd ~/Projects/AutoBDD-example -AutoBDD_Ver=3.0.0 ABDD_PROJECT=AutoBDD-example \ +AutoBDD_Ver=3.0.0 ABDD_PROJECT=AutoBDD-example \ # 3.0.0 = the framework line USER=$(whoami) PASSWORD=ubuntu HOSTOS=Linux USERID=$(id -u) GROUPID=$(id -g) \ docker compose run --rm autobdd-example-run "make e2e-test" # (see the test repo's docker-compose.yml / README for its exact run service) @@ -148,7 +161,8 @@ translated and warns on **stderr only**, so a v1 consumer keeps working untouche |---------|--------|-------------|------|---------| | **v2.3.0** | 96 | 7 | 12 | original pinned runtime (Node 12.22.7 + Chrome 96) | | **v2.4.0** | modern (latest stable, e.g. 153) | 7 | 14 | re‑activated v2.3.0 line; builds against current Chrome; matching browser driver baked into the image | -| **v3.0.0** | modern (latest stable, e.g. 153) | 9 | 20 | runtime re‑baseline: Node 20 + Java 17 + WebdriverIO 9 + Oculix 4.0.0 (image matching/OCR) | +| **v3.0.0** | modern (latest stable, e.g. 153) | 9 | 20 | runtime re‑baseline: Node 20 + Java 17 + WebdriverIO 9 + Oculix 4.0.0 (image matching/OCR) — **last framework‑line release; frozen** | +| **v4.0.0** | — | — | 24 | **base line**: Ubuntu 24.04 + Java 17 + Node 24 + Oculix 4.0.0, the `autobdd` front door with a verb vocabulary, self‑contained Oculix natives, and a 44‑feature conformance suite. No browser, no runner — a GUI you drive from higher‑level tools. | > From the next release the product is published as two tags — **`xyteam/autobdd-base`** > (screen‑only) and **`xyteam/autobdd-framework`** (the full product), with diff --git a/package-lock.json b/package-lock.json index df0a583..d3ba390 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "autobdd", - "version": "3.0.0", + "version": "4.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autobdd", - "version": "3.0.0", + "version": "4.0.0", "license": "ISC", "dependencies": { "ajv": "^8.6.2", diff --git a/package.json b/package.json index aed10e1..5542195 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "autobdd", - "version": "3.0.0", + "version": "4.0.0", "description": "BDD Automaiton", "scripts": { "test-init": ". ./.autoPathrc.sh && cd ./test-projects/autobdd-framework-test && xvfb-runner.sh make e2e-single-runner", diff --git a/seam/src/findTargetImage.js b/seam/src/findTargetImage.js index 6be9791..2385f97 100755 --- a/seam/src/findTargetImage.js +++ b/seam/src/findTargetImage.js @@ -109,11 +109,14 @@ ${rows} function printList() { process.stdout.write(FLOWS.map(([what, cmd]) => `${cmd}\n # ${what}\n`).join('')); } function version() { - let built = 'unknown'; - try { built = (fs.readFileSync('/etc/autobdd-versions', 'utf8').match(/^built=(.*)$/m) || [])[1] || 'unknown'; } catch (e) {} - return `findTargetImage — AutoBDD base seam -image : built ${built} -oculix : ${process.env.OCULIX_VER || '4.0.0'} + const stamp = (key, dflt) => { + try { return (fs.readFileSync('/etc/autobdd-versions', 'utf8').match(new RegExp('^' + key + '=(.*)$', 'm')) || [])[1] || dflt; } + catch (e) { return dflt; } + }; + return `autobdd — AutoBDD base image +version: ${stamp('version', 'unknown')} +image : built ${stamp('built', 'unknown')} +oculix : ${stamp('oculix', process.env.OCULIX_VER || '4.0.0')} node : ${process.version} `; } diff --git a/test-projects/autobdd-base-test/base-test/features.sh b/test-projects/autobdd-base-test/base-test/features.sh index 771d0c9..a0c518e 100755 --- a/test-projects/autobdd-base-test/base-test/features.sh +++ b/test-projects/autobdd-base-test/base-test/features.sh @@ -144,6 +144,7 @@ feat_provenance(){ check_has "records node=" "$f" "node=" check_has "records ubuntu_digest=" "$f" "ubuntu_digest=sha256:" check_has "records built=" "$f" "built=" + check_has "records the product version" "$f" "version=4.0.0" } # --------------------------------------------------------------------------- @@ -416,8 +417,8 @@ now_ms(){ echo $(( $(date +%s%N) / 1000000 )); } feat_help(){ feature help local t0 t1 out rc ms - t0=$(now_ms); out="$(findTargetImage --help 2>/dev/null)"; rc=$?; t1=$(now_ms); ms=$(( t1 - t0 )) - printf ' \033[2mrun: findTargetImage --help [rc=%s, %s ms]\033[0m\n' "$rc" "$ms" >&2 + t0=$(now_ms); out="$("$TARGET_BIN" --help 2>/dev/null)"; rc=$?; t1=$(now_ms); ms=$(( t1 - t0 )) + printf ' \033[2mrun: %s --help [rc=%s, %s ms]\033[0m\n' "$TARGET_BIN" "$rc" "$ms" >&2 check_eq "exit status is 0" "$rc" "0" check_has "prints the flows" "$out" "FLOWS" check_has "prints the canonical flags" "$out" "--match-image" @@ -433,17 +434,19 @@ feat_help(){ feat_version(){ feature version local out rc - out="$(findTargetImage --version 2>/dev/null)"; rc=$? - printf ' \033[2mrun: findTargetImage --version [rc=%s]\033[0m\n' "$rc" >&2 + out="$("$TARGET_BIN" --version 2>/dev/null)"; rc=$? + printf ' \033[2mrun: %s --version [rc=%s]\033[0m\n' "$TARGET_BIN" "$rc" >&2 check_eq "exit status is 0" "$rc" "0" - check_has "names the seam" "$out" "AutoBDD base seam" + check_has "names the product" "$out" "AutoBDD base image" + # The release gate: this is the version the image was built as, recorded at build time. + check_has "reports the version" "$out" "version: 4.0.0" check_has "reports the image build stamp" "$out" "image : built " } feat_list(){ feature list local out rc - out="$(findTargetImage --list 2>/dev/null)"; rc=$? - printf ' \033[2mrun: findTargetImage --list [rc=%s]\033[0m\n' "$rc" >&2 + out="$("$TARGET_BIN" --list 2>/dev/null)"; rc=$? + printf ' \033[2mrun: %s --list [rc=%s]\033[0m\n' "$TARGET_BIN" "$rc" >&2 check_eq "exit status is 0" "$rc" "0" check_has "lists the screen read as its own verb" "$out" "read-text" check_has "lists the picture flow" "$out" "--match-image=logo.png" @@ -454,8 +457,8 @@ feat_usage_error(){ local out rc # An unusable value must fail loudly: silently behaving like the default would look # like a working call to an agent. - out="$(findTargetImage --min-score=abc 2>&1 >/dev/null)"; rc=$? - printf ' \033[2mrun: findTargetImage --min-score=abc [rc=%s]\033[0m\n' "$rc" >&2 + out="$("$TARGET_BIN" --min-score=abc 2>&1 >/dev/null)"; rc=$? + printf ' \033[2mrun: %s --min-score=abc [rc=%s]\033[0m\n' "$TARGET_BIN" "$rc" >&2 check_eq "exit status is 2 (usage error)" "$rc" "2" check_has "names the offending flag" "$out" "--min-score" check_has "says what it expected" "$out" "expects a number" From 19501753fc83f70080133aa6ec20cb3733bac53c Mon Sep 17 00:00:00 2001 From: xywang68 Date: Tue, 22 Sep 2026 15:38:20 -0700 Subject: [PATCH 3/4] ci: derive the version from package.json and make the suite gate it properly CI failed on the previous commit, correctly: - provenance: records the product version (missing 'version=4.0.0' in: version=dev - version: reports the version (missing 'version: 4.0.0' in: version: dev The gate I wrote was the wrong shape: it asserted a hardcoded literal, but the image records the version it was BUILT as, and CI builds with --build-arg AUTOBDD_VERSION=dev. 'dev' is not a lie -- AUTOBDD_VER is simultaneously the image tag and the base tag the framework image pins via FROM, so a pull-request build legitimately stamps dev. Fix, in two parts: suite (features.sh) - provenance now asserts the stamp records a non-empty version (not 'unknown'), and additionally that it equals EXPECTED_VERSION when the caller declares one. Unset, the release gate is dormant and the run is a consistency check. - version now asserts --version agrees with the image's own stamp, and equals EXPECTED_VERSION when declared. The suite therefore works unchanged for both a dev build and a release build, instead of hardcoding a number that will rot. workflow (conformance.yml) - AUTOBDD_VER is resolved from package.json in a first step (GITHUB_ENV) rather than being a literal 'dev': package.json is the single source of truth, and using one value for the build arg, the image tags and the suite's expectation is what makes a version drift fail the build instead of shipping. - the base-test step exports AutoBDD_Ver (the tag this workflow built, since the test projects' .env defaults to dev) and EXPECTED_VERSION; the framework step exports AutoBDD_Ver too. Docs: the test project's README documents EXPECTED_VERSION; CHANGELOG records the wiring. Verified all three cases, not just the happy one: 1. built as dev, no expectation -> 30 passed, 0 failed, 'consistency checked, no release gate' printed 2. built as dev, expecting 4.0.0 -> FAILS ('got dev, want 4.0.0'), exit 1 -- so the gate bites and the failure propagates 3. built as 4.0.0, expecting 4.0.0 -> 30 passed, 0 failed group I: '--version agrees with the image's own stamp = 4.0.0' and 'is the declared release = 4.0.0' --- .github/workflows/conformance.yml | 17 +++++++++++++- CHANGELOG.md | 6 ++++- test-projects/autobdd-base-test/README.md | 1 + .../autobdd-base-test/base-test/features.sh | 22 ++++++++++++++++--- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 56b8acc..53ae71e 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -18,7 +18,9 @@ on: workflow_dispatch: env: - AUTOBDD_VER: "dev" + # AUTOBDD_VER is resolved from package.json by the first step: it is the product version, + # the image tag, and (via --build-arg) the tag the framework image pins as its base. Using + # one value for all three is what lets the base suite gate the declared release. # NFR-S2 budgets, in GB of compressed pull size (see the sizes step for the metric). BASE_BUDGET_GB: "2.5" FRAMEWORK_BUDGET_GB: "3.5" @@ -34,6 +36,16 @@ jobs: steps: - uses: actions/checkout@v4 + # package.json is the single source of truth for the product version. Everything below + # derives from it: the build arg, the image tags, and the expectation the base suite + # gates on -- so a version drift anywhere fails the build instead of shipping. + - name: Resolve version + run: | + set -euo pipefail + ver="$(node -p "require('./package.json').version")" + echo "AUTOBDD_VER=${ver}" >> "${GITHUB_ENV}" + echo "### building ${ver}" >> "${GITHUB_STEP_SUMMARY}" + - name: Build autobdd-base + autobdd-framework run: | docker build --build-arg AUTOBDD_VERSION="${AUTOBDD_VER}" \ @@ -96,6 +108,8 @@ jobs: - name: autobdd-base-test (no browser) working-directory: test-projects/autobdd-base-test env: + AutoBDD_Ver: ${{ env.AUTOBDD_VER }} # the tag this workflow built + EXPECTED_VERSION: ${{ env.AUTOBDD_VER }} # the release the image must report USER: runner HOSTOS: Linux USERID: "1001" @@ -106,6 +120,7 @@ jobs: - name: autobdd-framework-test (e2e, baked image) working-directory: test-projects/autobdd-framework-test env: + AutoBDD_Ver: ${{ env.AUTOBDD_VER }} USER: runner HOSTOS: Linux USERID: "1001" diff --git a/CHANGELOG.md b/CHANGELOG.md index af4d7e8..428689e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -187,7 +187,11 @@ What that meant in practice, all in this release: value it observed. Writing it found and fixed six real defects in the engine, and five in the suite's own narration. * **The image reports what it is.** `version=` is recorded in `/etc/autobdd-versions` and - printed by `autobdd --version`; the suite gates on it. + printed by `autobdd --version`, and CI resolves that value from `package.json` so the + build arg, the image tags and the suite's expectation cannot drift: the base suite checks + that `--version` agrees with the image's own stamp, and — when `EXPECTED_VERSION` is set — + that both equal the declared release. Unset (a local run) it checks consistency only, so + the same suite works for a `dev` build and for a release build. The framework image (`xyteam/autobdd-framework`, and the `xyteam/autobdd` alias) remains on the 3.0.0 line; it pins its base via `AUTOBDD_VERSION`, so re-baselining it onto 4.0.0 is a diff --git a/test-projects/autobdd-base-test/README.md b/test-projects/autobdd-base-test/README.md index 7f06102..0a09054 100644 --- a/test-projects/autobdd-base-test/README.md +++ b/test-projects/autobdd-base-test/README.md @@ -53,6 +53,7 @@ commands above. Always go through `docker compose run` (or `make docker-run`). |---|---|---| | `AutoBDD_Ver` | `dev` (from `.env`) | the image tag: `xyteam/autobdd-base:`. Must match what you built. | | `TARGET_BIN` | `/usr/local/libexec/autobdd/find-target` | the entry point under test. Set to `findTargetImage` for the deprecated-alias surface. | +| `EXPECTED_VERSION` | unset | the release the image must report. Unset, the suite checks only that the image's reported version agrees with its own build stamp; set, a mismatch fails the run (CI sets it from `package.json`). | | `RESOLUTION` | `1920x1200x24` | Xvfb geometry. | | `NOSHOW` | unset | internal: timing features set it to keep the fixture re-show out of the measured window. | diff --git a/test-projects/autobdd-base-test/base-test/features.sh b/test-projects/autobdd-base-test/base-test/features.sh index a0c518e..2d1264c 100755 --- a/test-projects/autobdd-base-test/base-test/features.sh +++ b/test-projects/autobdd-base-test/base-test/features.sh @@ -144,7 +144,17 @@ feat_provenance(){ check_has "records node=" "$f" "node=" check_has "records ubuntu_digest=" "$f" "ubuntu_digest=sha256:" check_has "records built=" "$f" "built=" - check_has "records the product version" "$f" "version=4.0.0" + # The image records the version it was BUILT as, which is whatever AUTOBDD_VERSION the + # builder passed -- `dev` for a CI pull-request build, the release number otherwise. So + # assert consistency here, and gate on the declared version only when EXPECTED_VERSION + # says what that should be (the release job sets it from package.json). + local ver; ver="$(printf '%s' "$f" | sed -n 's/^version=//p')" + if [ -n "$ver" ] && [ "$ver" != "unknown" ]; then _ok "records the build version = $ver"; else _no "records the build version (got '$ver')"; fi + if [ -n "${EXPECTED_VERSION:-}" ]; then + check_eq "the build version is the declared release" "$ver" "$EXPECTED_VERSION" + else + printf ' \033[2m EXPECTED_VERSION unset: consistency checked, no release gate\033[0m\n' >&2 + fi } # --------------------------------------------------------------------------- @@ -438,9 +448,15 @@ feat_version(){ printf ' \033[2mrun: %s --version [rc=%s]\033[0m\n' "$TARGET_BIN" "$rc" >&2 check_eq "exit status is 0" "$rc" "0" check_has "names the product" "$out" "AutoBDD base image" - # The release gate: this is the version the image was built as, recorded at build time. - check_has "reports the version" "$out" "version: 4.0.0" check_has "reports the image build stamp" "$out" "image : built " + # --version and /etc/autobdd-versions must agree: one is the other, reported. + local printed stampver + printed="$(printf '%s' "$out" | sed -n 's/^version: //p')" + stampver="$(sed -n 's/^version=//p' /etc/autobdd-versions 2>/dev/null)" + check_eq "--version agrees with the image's own stamp" "$printed" "$stampver" + if [ -n "${EXPECTED_VERSION:-}" ]; then + check_eq "--version is the declared release" "$printed" "$EXPECTED_VERSION" + fi } feat_list(){ feature list From 82f6535ba5c73f11f58bc277a18ea0bf37f25c9b Mon Sep 17 00:00:00 2001 From: xywang68 Date: Tue, 22 Sep 2026 18:01:45 -0700 Subject: [PATCH 4/4] seam: do not paint a highlight when --flash=0s HARDENING, NOT A PROVEN FIX -- read this before treating it as one. The intermittent exit-status defect recorded twice in this project (rc=139, SIGSEGV, on a call that had already written a correct result) is NOT reproduced by this change, and I could not reproduce it at all to diagnose it: 20 sequential calls and 24 calls at 6-way concurrency inside a container, 44 runs, every one rc=0 with empty stderr. Both historical sightings happened on an abnormally loaded host (three conformance suites and zombie containers plus a build running at once), and in both cases the crash output was never captured, so there is no artifact to work from. What this does remove is the one place the engine races a native thread against process teardown, and it sits on what is now the default automation path: Region.highlight() paints on a background native thread and returns immediately, and with --flash=0s the engine painted and then slept 0 ms -- exiting while that painter may still be running. --flash=0 means 'no flash', so painting was pointless work as well as a race. The helper now returns before painting when the pause is zero. Nothing visible is lost: the caller asked for no flash. Verified: the flash feature still measures the default path (default 2128 ms vs --flash=0s 1131 ms -> delta 997 ms, bounded), and the full suite runs 135 passed, 0 failed with EXPECTED_VERSION=4.0.0 set. If rc=139 recurs, capture stderr and the payload together -- the run line already records [rc=N] -- and the next candidate is the JVM/native teardown itself (skipping it on the success path), which cannot be chosen responsibly without that evidence. --- seam/src/findTargetImage.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/seam/src/findTargetImage.js b/seam/src/findTargetImage.js index 2385f97..113fb9d 100755 --- a/seam/src/findTargetImage.js +++ b/seam/src/findTargetImage.js @@ -299,6 +299,11 @@ const notFoundStatus = {status: 'notFound'}; // it ever paints. Flash for a visible duration and hold the process so the box // actually renders. const flashOnMatch = (region) => { + // --flash=0 means "no flash", so do not paint at all. Region.highlight() paints on a + // background native thread and returns immediately; painting and then exiting 0 ms later + // races that thread against process teardown, on what is now the default automation path. + // Nothing visible is lost: the caller asked for no flash. + if (flashSecs <= 0) return; region.highlight(); sleepMs(Math.round(flashSecs * 1000)); };