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/.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 05b1b51..428689e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -159,6 +159,44 @@ 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`, 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 +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 05ba649..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) @@ -39,20 +52,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 +101,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. - -### Example usages - -* **Detect a word and get its bounding box (no action)** - - ```bash - findTargetImage --ocrPath="SUBMIT" --ocrDetail=word - ``` +### Arguments -* **Click the first occurrence of a word** +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: - ```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 @@ -194,7 +161,8 @@ an empty array – existing parsers that ignore this field see no change. |---------|--------|-------------|------|---------| | **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 @@ -235,18 +203,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 +252,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/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/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..2385f97 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')} @@ -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/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 55d31ee..2d1264c 100755 --- a/test-projects/autobdd-base-test/base-test/features.sh +++ b/test-projects/autobdd-base-test/base-test/features.sh @@ -144,6 +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=" + # 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 } # --------------------------------------------------------------------------- @@ -335,12 +346,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 @@ -416,11 +427,12 @@ 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 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" @@ -432,17 +444,25 @@ 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" 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 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" @@ -453,8 +473,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"