Something went wrong. Try again.
Something went wrong. Try again.
6.0 kB · 140 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141import { afterEach, describe, expect, it } from "vitest";import { readFileSync } from "node:fs";import { spawnSync } from "node:child_process";import { resolve } from "node:path";import { runCli } from "../../runner/cli";import { parseScript } from "../../runner/parser";import { printScript } from "../../runner/printer";import { HELLO, makeProject, type Project } from "../helpers/project";
/** Real script files, read from disk by the real `aite run` and by the parser directly. No browser: scripts are refused before one starts. */
const root = resolve(__dirname, "../..");const projects: Project[] = [];afterEach(() => void projects.splice(0).forEach((p) => p.repo.cleanup()));const project = async (script: (o: string) => string) => { const p = await makeProject({ "hello.html": HELLO }, script); projects.push(p); return p;};
const KITCHEN_SINK = (o: string) => `# every command of the format oncetest "kitchen sink" covers J-01 J-02 start ${o}/hello.html needs session p timeout 4s
step "pointing and typing" goto ${o}/hello.html doubleClick css:".row" check label:"Agree" uncheck label:"Agree" type placeholder:"Search" "alex" select label:"Color" "Dark blue" press Enter drag text:"Card" to:text:"Done" step "values" set total "5" capture before css:".balance" verify css:".balance" text:"\${total}" within:"Panel" verifyNot css:".error" verify label:"Name" value:"alex" verify role:button name:"Go" enabled verify css:".row" count:3 verify title:"Mallbucks" verify url~:"/gift/" verify css:".balance" number:changed-by from:before equals:-1 timeout:5s step "waiting and tabs" wait css:".spin" gone wait css:".n" text-changes wait url~:"/claim" wait network-idle timeout:10s sleep 2s switchTab url~:"/pay" switchTab new closeTab screenshot "after pay" call balanceMatches 3 "a b" run shared/login.aite`;
describe("a script using every command", () => { it("parses with no syntax error: aite run goes on to start the browser (which is not there in this test)", async () => { const p = await project(KITCHEN_SINK); p.repo.write("fixtures/shared/login.aite", 'test "login"\nstep "x"\n press Tab\n'); // the script's `run shared/login.aite` const r = await runCli(["run", p.scriptPath, "--config", p.configPath]); expect(r.exitCode).toBe(3); expect(r.stderr).not.toMatch(/E00[1-7]|unknown command|unknown target key|needs a|not supported yet|t\.aite:\d+:/); });
it("prints back as text that reads to the same script (comments and spacing aside)", async () => { const p = await project(KITCHEN_SINK); const first = parseScript(readFileSync(p.scriptPath, "utf8")); if (!first.ok) throw new Error(JSON.stringify(first.errors)); const again = parseScript(printScript(first.script)); if (!again.ok) throw new Error(JSON.stringify(again.errors)); const strip = (v: unknown) => JSON.parse(JSON.stringify(v, (k, x) => (/^(line|startLine|needsLine)$/.test(k) ? undefined : x))); expect(strip(again.script)).toEqual(strip(first.script)); expect(first.script.steps.flatMap((s) => s.commands)).toHaveLength(29); });});
describe("the repository's own scripts and the spec's example", () => { it("fixtures/hello.aite parses", () => { expect(parseScript(readFileSync(resolve(root, "fixtures/hello.aite"), "utf8")).ok).toBe(true); }); it("the example in script-format.md parses to the tree the spec describes", () => { const spec = readFileSync(resolve(root, "docs/design/script-format.md"), "utf8"); const example = /## Example\n\n```\n([\s\S]*?)```/.exec(spec)![1]!; const r = parseScript(example); if (!r.ok) throw new Error(JSON.stringify(r.errors)); expect(r.script).toMatchObject({ name: "send 1 MALLBUCKS to a handle", covers: ["J-09", "J-13"], start: "https://mallbucks.malldao.xyz/", needsSession: "test-qian" }); expect(r.script.steps.map((s) => s.commands.map((c) => c.verb))).toEqual([["capture"], ["click", "type", "click"], ["type", "click", "verify"], ["verify"]]); expect(r.script.steps[3]!.commands[0]).toMatchObject({ kind: "numberChange", from: "before", equals: -1, timeoutMs: 5000 }); });});
describe("a script with many different mistakes", () => { it("lists each one with its own line, in line order", async () => { const p = await project( (o) => `test "t" start ${o}/hello.html step "s" clik text:"x" click txt:"y" verify type label:"a" press sleep soon set total click text:"a" text:"b" click index:0 verify css:".r" count:many type label:"a" "\${nope}" goto nowhere click text:"unterminated`, ); const r = await runCli(["run", p.scriptPath, "--config", p.configPath]); expect(r.exitCode).toBe(2); const lines = r.stderr.split("\n").filter((l) => /t\.aite:\d+:/.test(l)).map((l) => /t\.aite:(\d+):/.exec(l)![1]); expect(lines).toEqual(["4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"]); expect(r.stderr).toContain("13 errors in"); expect(r.stderr).toContain("t.aite:14: ${nope} is used before it is set"); });});
describe("tools/soakCli.ts as the executable", () => { const soak = (args: string[]) => spawnSync(resolve(root, "node_modules/.bin/tsx"), [resolve(root, "tools/soakCli.ts"), ...args], { cwd: root, encoding: "utf8", timeout: 60_000 }); it("--help prints the usage and exits 0", () => { const r = soak(["--help"]); expect(r.status, `exit ${r.status}; stderr: ${r.stderr}; stdout: ${String(r.stdout).slice(0, 300)}`).toBe(0); expect(r.stdout).toContain("usage: npm run soak:hour"); }); it("an unknown option exits 2 and says which", () => { const r = soak(["--turbo"]); expect(r.status, `exit ${r.status}; stderr: ${r.stderr}; stdout: ${String(r.stdout).slice(0, 300)}`).toBe(2); expect(r.stderr).toContain('unknown option "--turbo"'); });});