effiTest UI Code Generator
This skill converts UI screenshots + a test-case document into compileable Java automation code for the effiTest hybrid framework (effiTest wrappers preferred; raw Selenium only as fallback). It handles both new-file generation and surgical updates to an existing Page / Test class.
When to invoke
Trigger whenever the user wants Page/Test automation code generated or updated. They must supply at least one of --images or --testcases, always a page path flag, and a test path flag only when test cases are supplied.
Inputs
| Flag | Meaning |
|---|---|
--page-classpath <path> |
Create new Page class at this path |
--update-page-class <path> |
Update existing Page class at this path |
--test-classpath <path> |
Create new Test class at this path (required only with --testcases) |
--update-test-class <path> |
Update existing Test class at this path (required only with --testcases) |
--testng-xmlpath <path> |
Optional; honored only when a Test class is being generated |
--feature-name <name> |
Required on first run for a page path (cached after) |
--images <glob-or-dir> |
One or more UI screenshots. Optional if --testcases given. |
--testcases <file> |
txt / csv / xlsx of test cases. Optional if --images given. |
Validation:
- Page pair is always required: exactly one of (
--update-page-class,--page-classpath). - Test pair is required only when
--testcasesis supplied: exactly one of (--update-test-class,--test-classpath). - At least one of
--images/--testcasesmust be supplied. parse_args.pyenforces all of the above and prints aflowfield telling you which mode to run.
Flow modes
parse_args.py emits a flow field in its JSON output — branch on it:
full— both--imagesand--testcasessupplied. Run all phases (A→F) as documented below.page-only— only--imagessupplied. Run phases A, B, C. Skip phases D (test class), E's test-class compile checks (still compile the Page alone), and F (testng xml is ignored).testcases-only— only--testcasessupplied. Run phase A. Skip phase B (no OCR). In phase C, read the existing Page class to learn what fields/methods already exist; in phase D, parse test cases and map each step to a Page method — for any step whosetargethas no matching field or method in the Page, stub it into the Page: addBy <name> = null;and an empty action method with the right signature (see "Stubbing rules" below). Then emit the Test class. Compile and fix as usual.
Workflow overview
The skill runs six phases. Do not skip phases: each later phase depends on state from the earlier ones. Run the bundled scripts rather than re-implementing their logic inline — they are deterministic and reused across invocations.
Phase A: Init → parse args, ensure config.json, (re)build effitest_methods.json
Phase B: OCR per image → spawn vision subagents, write per-image field JSONs (+ tesseract merge if low-confidence)
Phase C: Page class → create or update <feature>_Page.java (effiTest-first)
Phase D: Test class → parse test cases → map steps → emit <feature>_Test.java
Phase E: Compile & fix → mvn test-compile loop (max 5); on exhaustion, summarize root cause
Phase F: TestNG xml → render assets/testng_template.xml if --testng-xmlpath supplied
All absolute paths below assume the skill root is the directory containing this SKILL.md. All project-relative paths resolve against the working directory (the effitest project root).
Phase A — Init
Run the arg parser:
python scripts/parse_args.py <all forwarded flags> --project-root <cwd>It prints a single JSON blob on stdout with normalized paths and a resolved
feature_name(looking up the cache if--feature-namewasn't passed). If validation fails, it exits non-zero with a human-readable error — surface that and stop.Build / refresh the method catalog:
python scripts/init_methods_catalog.py \ --project-root <cwd> \ --config <cwd>/.claude/effiTest-code-gen/config.json \ --methods-out <cwd>/.claude/effiTest-code-gen/effitest_methods.jsonThis script:
- Creates
<project>/.claude/effiTest-code-gen/config.jsonwith{"effiTest.version": null, "last used time": "<ISO>"}if missing. - Reads the pom, finds the effitest dependency version.
- If
config.effiTest.versionmatches andeffitest_methods.jsonexists, it just updateslast used timeand exits fast. - Otherwise it locates the jar under
~/.m2/repository/..., runsjavapon the curated class list fromscripts/effitest_paths.py, and writes the catalog in the exact schema the spec demands.
If the script reports the jar isn't in
~/.m2(fresh clone, user hasn't built), tell the user to runmvn -q dependency:resolveand rerun. Don't fabricate methods.- Creates
Phase B — OCR per image (skip entirely in testcases-only)
For each image path resolved by parse_args.py (the images array in the JSON), spawn one Explore subagent per image, in parallel, up to 3 at a time. Use agents/ocr_vision.md as the subagent prompt, injecting the image path and the target JSON output path:
<project>/.claude/effiTest-code-gen/ocr/<feature_name>_<image-stem>.json
When all subagents return, inspect each JSON. If a JSON has "confidence": "low" or is missing critical fields the user's test cases reference, merge in tesseract results:
python scripts/ocr_fallback.py --image <image-path> --merge-into <json-path>
ocr_fallback.py no-ops gracefully if pytesseract / Pillow / the tesseract binary are missing (prints a single warning line, exit 0). Do not block the pipeline on its absence.
Phase C — Page class (new or update)
Read everything you need into context:
- The catalog at
<project>/.claude/effiTest-code-gen/effitest_methods.json. - (If any) each per-image OCR JSON.
- The existing Page file if update mode.
references/generation_patterns.md(effitest-first mapping rules and the class skeleton).
Then, based on flow:
full or page-only — create mode (--page-classpath): emit a full class at that path. Derive the package by taking the portion of the path after src/main/java/ or src/test/java/ and replacing / with .. The class name is <feature_name>_Page. Add one By <fieldName> = null; per OCR field (camelCase, unique), plus one action method per field (enter<Field>, click<Field>, select<Field>, toggle<Field>, etc., driven by the field type). For each action, prefer a matching method from the effitest catalog; only emit raw Selenium when no match exists. Imports: include only what you use.
full or page-only — update mode (--update-page-class): read the existing file, compute the set of already-declared By fields (parse the file contents — don't guess), and append only the missing fields and their action methods. Preserve the existing package line, imports, brace style, and surrounding code. Add any new imports the new methods require, placed with the existing import block. Do not reformat unrelated code.
testcases-only: there's no OCR JSON to drive the Page. Read the existing Page file (or, in create mode, start an empty one at --page-classpath). Then during phase D, as each test step is mapped, stub into the Page any missing field/method (see "Stubbing rules" in references/generation_patterns.md). Don't generate speculative stubs before phase D — wait until a test step actually references them, so the Page only grows what's actually used.
The exact skeleton and the effitest-first decision rule live in references/generation_patterns.md — read it before generating.
Phase D — Test class (skip entirely in page-only)
Normalize the test cases:
python scripts/parse_testcases.py --file <testcases path> --out <project>/.claude/effiTest-code-gen/<feature>_testcases.jsonEmits
[{id, title, preconditions, steps:[{action, target, data, expected}], postconditions}]. For.txtit uses a line-heuristic; for.csvit relies on headers (id,title,step,data,expected,...); for.xlsxit usesopenpyxl. Ifopenpyxlis unavailable, the script exits with a clear message — tell the user topip install openpyxland stop.Bind each step to a Page method. For every step, locate a matching field (or method) on the current Page class by matching
step.target(normalized to camelCase) against declaredBynames and existing method names. If a match exists, call the method. If not:- In
fullflow, this should be rare — note it as a warning and fall back to stubbing (same rule as below). - In
testcases-onlyflow, stub the Page: append aBy <name> = null;declaration and an empty action method with the correct signature (public void <verb><Name>(String value) {}for data-entry verbs,public void <verb><Name>() {}for click/toggle,public String <verb><Name>() { return null; }for verify/read). Leave a// TODO: implement from <feature>_Page stub — raised from test case <TC-id>comment inside the body. After stubbing, re-emit the Page file and then call the method from the test.
- In
Emit
<feature>_Test.javaat the configured test path:- One
@Testper test case, method name = camelCasedtitle(ortc_<id>fallback). - Preconditions →
@BeforeMethod; postconditions →@AfterMethod. If the project appears to have a base test class (detectable by aBaseTest/TestBasefile sibling to the target path), extend it and skip the lifecycle hooks. - Each step calls a method on the
<feature>_Pageinstance; only use methods that exist in the Page class after stubs have been applied. - Each step with an
expectedbecomes an assertion — prefer anEffiAssert-style method from the catalog; fall back toorg.testng.Assert.*. If the Page has no verify-style getter, stub one (see above) so the assertion has something to read.
- One
Final report for
testcases-onlyflow should list every stubbed field and method so the human knows what to fill in:⚠ Stubbed in Page (needs human implementation): • By captchaInput — added from TC03 step 2 • void enterCaptchaInput(String value) — empty body • String getErrorBanner() — returns null
Phase E — Compile & fix loop
python scripts/compile_check.py --project-root <cwd>
Returns JSON: {"ok": bool, "errors": [{"file","line","message"}, ...], "raw": "<trailing maven output>"}.
If ok is false, for each error, open the file, read the offending line ± 5 lines, and patch only what's needed. Rerun the script. Iterate at most 5 times. If still failing:
- Print a concise summary: the top 3 unique error messages, the files they touch, and a one-line "likely cause / suggested fix" for each (e.g. "effitest method signature drift → regenerate catalog with
rm .claude/effiTest-code-gen/effitest_methods.jsonand rerun", "missing pom dependency → add<dependency>...for X"). - Leave the generated files on disk — the user may want to inspect and hand-fix.
Phase F — TestNG xml
Only if --testng-xmlpath was supplied. Read assets/testng_template.xml, replace {{SUITE_NAME}}, {{CLASS}} (FQCN of the generated test class), {{LISTENERS}} (comma-less XML snippet from effitest_paths.LISTENERS_BY_MAJOR, keyed on the effitest major version parsed from the pom — fall back to the default entry and log a warning if unknown). Write to --testng-xmlpath.
Final report to the user
Print a compact summary:
✔ Page: <absolute path> (<N added fields, 0 removed>)
✔ Test: <absolute path> (<N test methods>)
✔ XML: <absolute path or "skipped">
✔ Catalog: effiTest <version> (<M classes, K methods>) [regenerated | cached]
✔ Compile: OK | 5 iterations exhausted — see errors above
Tips
- Always run scripts via
python scripts/<name>.pyfrom the skill directory, passing absolute project paths — keep the skill location-independent. - Field names must be deterministic: snake from the visible label, then camelCase. If two fields would collide, disambiguate with a trailing index (
email,email2) and log the collision. - Never invent effitest methods. If the needed action isn't in the catalog, emit the Selenium fallback and leave a
// TODO: effitest equivalent?comment so humans can triage. - When updating an existing file, never re-order existing members — append only. This keeps diffs reviewable.
- For OCR, bias toward semantic field names (e.g.
usernameInput) over layout ones (textField1). The vision subagent prompt enforces this.
Files in this skill
scripts/parse_args.py— CLI validation & normalization.scripts/effitest_paths.py— curated FQCN list, description overrides, listener version map.scripts/init_methods_catalog.py— pom → m2 jar → javap → methods JSON.scripts/parse_testcases.py— txt/csv/xlsx normalizer.scripts/ocr_fallback.py— pytesseract fallback merger.scripts/compile_check.py—mvn test-compilewrapper.agents/ocr_vision.md— prompt for the per-image vision subagent.assets/testng_template.xml— TestNG suite template.references/generation_patterns.md— Page/Test generation rules & skeleton.references/examples.md— worked end-to-end example.