{"id":49550397,"url":"https://github.com/toolstackhq/harness","last_synced_at":"2026-05-02T22:05:56.844Z","repository":{"id":353524535,"uuid":"1219775371","full_name":"toolstackhq/harness","owner":"toolstackhq","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-24T08:20:46.000Z","size":145,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-24T10:30:44.591Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/toolstackhq.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T07:52:08.000Z","updated_at":"2026-04-24T08:20:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/toolstackhq/harness","commit_stats":null,"previous_names":["toolstackhq/harness"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/toolstackhq/harness","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolstackhq%2Fharness","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolstackhq%2Fharness/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolstackhq%2Fharness/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolstackhq%2Fharness/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toolstackhq","download_url":"https://codeload.github.com/toolstackhq/harness/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toolstackhq%2Fharness/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32550939,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T21:31:48.061Z","status":"ssl_error","status_checked_at":"2026-05-02T21:31:46.574Z","response_time":132,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-02T22:05:31.698Z","updated_at":"2026-05-02T22:05:56.838Z","avatar_url":"https://github.com/toolstackhq.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/icon.png\" width=\"120\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eHarness\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eRecord once. Ship a script or a doc. Replay forever.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/toolstackhq/harness/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/toolstackhq/harness?style=flat\u0026color=yellow\" alt=\"Stars\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/toolstackhq/harness/commits/main\"\u003e\u003cimg src=\"https://img.shields.io/github/last-commit/toolstackhq/harness?style=flat\" alt=\"Last commit\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"License: MIT\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#install\"\u003eInstall\u003c/a\u003e •\n  \u003ca href=\"#what-it-does\"\u003eWhat it does\u003c/a\u003e •\n  \u003ca href=\"#export-menu\"\u003eExport menu\u003c/a\u003e •\n  \u003ca href=\"#frameworks\"\u003eFrameworks\u003c/a\u003e •\n  \u003ca href=\"#dynamic-values\"\u003eDynamic values\u003c/a\u003e •\n  \u003ca href=\"#selector-only-export\"\u003eSelector export\u003c/a\u003e •\n  \u003ca href=\"#llm-prompt-export\"\u003eLLM prompt\u003c/a\u003e •\n  \u003ca href=\"#inspector\"\u003eInspector\u003c/a\u003e •\n  \u003ca href=\"#replay\"\u003eReplay\u003c/a\u003e •\n  \u003ca href=\"#folders\"\u003eFolders\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nClick around in a real browser. Harness watches via CDP and turns the\nsession into a runnable test script or an annotated walkthrough doc.\nReplay it whenever you want to check the flow still works.\n\n\u003e 🤖 **Record once, hand it to any LLM.** Pick framework + language + your\n\u003e LLM, click **Copy to clipboard**, paste into Claude / GPT / Gemini.\n\u003e Get back a runnable test for any framework, even ones Harness doesn't\n\u003e ship.\n\u003e\n\u003e \u003cdetails\u003e\n\u003e \u003csummary\u003e\u003cstrong\u003eClick for a real generated prompt\u003c/strong\u003e\u003c/summary\u003e\n\u003e\n\u003e ```\n\u003e You are Claude. Reason carefully but keep the final code production-quality, idiomatic, and minimal.\n\u003e You are also a senior test automation specialist.\n\u003e\n\u003e # Task\n\u003e Convert the recorded user flow below into a runnable **WebdriverIO** test, written in **TypeScript**.\n\u003e\n\u003e ## Project context\n\u003e - This will land in an **existing test suite**. Assume the framework, runner, base URL config, fixtures, helpers and folder convention are already set up.\n\u003e - Do NOT regenerate package.json, framework config, tsconfig, .env, or CI workflows. Do not change any existing file unless strictly necessary.\n\u003e - Match whatever Page Object / fixture / data-builder pattern the existing suite uses if hinted in the notes below; otherwise stay close to the framework's idiomatic test shape.\n\u003e - Output a single new test file as one fenced block, prefixed with the suggested file path comment.\n\u003e\n\u003e ## Requirements\n\u003e - Use the exact selectors provided. Prefer the most stable form (id / data-testid / role) when there are alternatives.\n\u003e - Treat any value that looks like sample data (emails, names, account numbers) as a parameter. Hoist it to a constant or test parameter at the top of the file.\n\u003e - Add explicit waits for visibility / network idle where appropriate; do not insert blind sleeps unless the recorded flow had a wait step.\n\u003e - For shadow DOM selectors written as `host \u003e\u003e child`, translate them into the framework's pierce syntax (e.g. Playwright's `\u003e\u003e`, Cypress `.shadow().find()`, Selenium's JS executor).\n\u003e - Wrap the whole flow in a single test or describe block named after the recorded URL.\n\u003e - If the framework needs imports / driver bootstrap, generate them. If driver setup depends on the user's machine (Selenium-Java for instance), explicitly call that out as a comment.\n\u003e - Output only the final code in a fenced block, then a 2-3 line explanation. No marketing.\n\u003e\n\u003e ## Extra notes from the user\n\u003e Wrap interactions in a Page Object Model. Target staging only.\n\u003e\n\u003e ## Recorded flow\n\u003e 1. Navigate to `https://staging.example.com/login`.\n\u003e 2. Fill \"Email\" (selector: `#email`) with the value `alice@example.com`.\n\u003e 3. Fill \"Password\" (selector: `#password`) with the value `hunter2`.\n\u003e 4. Click on \"Sign in\" (selector: `button[type='submit']`).\n\u003e 5. Assert that \"Welcome back\" (selector: `.welcome`) is visible.\n\u003e 6. Click on \"Account\" (selector: `nav-bar \u003e\u003e #account-link`).\n\u003e 7. Assert that selector `[data-testid=\"balance\"]` contains the text `$`.\n\u003e\n\u003e Return the test now.\n\u003e ```\n\u003e\n\u003e \u003c/details\u003e\n\n![Harness startup screen](assets/harness-startup.png)\n\n## What it does\n\n- Test scripts. Playwright, Cypress, Selenium (JavaScript), Selenium (Java), or your own custom template.\n- Walkthroughs. Per-step screenshots exported as HTML, PDF, Markdown, WebM or MP4 with bullet-list slides per page.\n- Element capture. Hover, click, get just that element. Mark up with pen, highlighter, shapes, text via `tui-image-editor`.\n- Replay. Built-in CDP runner with per-step pass/fail and scroll-into-view on every action.\n- Dynamic values. `{{random.email}}`, `{{random.uuid}}`, `{{timestamp}}` etc. Fresh value every replay, baked into exported scripts as runtime expressions. Add your own.\n- Folders. Postman-style. Drag a recording onto a folder chip to file it. Filter, rename, delete.\n- Selector-only export. CSV, JSON, YAML, or XML for object-repository workflows.\n- LLM prompt export. Hand the recorded flow off to Claude / GPT / Gemini for any framework Harness doesn't ship directly.\n- Inspector. Right-click any element in the embedded browser to see its selector plus a full attribute table.\n\n## Install\n\n```bash\ngit clone https://github.com/toolstackhq/harness.git\ncd harness\nnpm install\nnpm run dev\n```\n\n`npm run dev` boots Vite + Electron with hot reload. For a packaged build:\n\n```bash\nnpm run build:renderer\nnpm start\n```\n\nSessions persist under `~/.config/Harness/`.\n\n## Export menu\n\nOpen any saved session, hit **Export ▾**. Three categories:\n\n- **Test script** — direct codegen for the framework saved with the session. Copy or save as a file.\n- **Selectors** — CSV / JSON / YAML / XML for object-repository workflows.\n- **LLM prompt** — opens a small dialog asking for framework, language, target LLM, and any extra notes. Output copies to clipboard or saves as `.txt`.\n\nWalkthrough docs (HTML / PDF / Markdown / WebM / MP4) keep their own dialog from the active recording session toolbar.\n\n## Frameworks\n\n| Target | Language | Output snippet |\n|--------|----------|----------------|\n| Playwright | JavaScript or TypeScript | `await page.locator('#x').fill(EMAIL)` |\n| Cypress | JavaScript or TypeScript | `cy.get('#x').clear().type(EMAIL)` |\n| Selenium (JS) | JavaScript | `await driver.findElement(By.css('#x')).sendKeys(EMAIL)` |\n| Selenium (Java) | Java | `el0.sendKeys(EMAIL);` snippet, no class wrapper |\n| Custom | Whatever you map to | Your template, your placeholders |\n\nThe Java target generates the action body only. Driver setup, imports\nand lifecycle stay your job because every machine is different.\n\n## Dynamic values\n\nIn the step editor, click **Insert dynamic value**. Pick from the\nlabelled list. The token gets inserted at the cursor and translated\ninto runtime code in whatever framework you export.\n\n| Token | Replay value | JS export | Java export |\n|-------|--------------|-----------|-------------|\n| `{{random.number}}` | `4827193` | `Array.from({length:7},...)` | `String.format(\"%07d\", ThreadLocalRandom...)` |\n| `{{random.alpha:8}}` | `qjflxzpr` | inline 26-char picker | `IntStream.range...joining` |\n| `{{random.uuid}}` | `f81d4fae-...` | `crypto.randomUUID()` | `UUID.randomUUID().toString()` |\n| `{{random.email}}` | `user_\u003cts\u003e_\u003cn\u003e@example.com` | template literal | string concat |\n| `{{timestamp}}` | `1714045932148` | `String(Date.now())` | `String.valueOf(System.currentTimeMillis())` |\n| `{{date.iso}}` | `2026-04-27T09:32:12.148Z` | `new Date().toISOString()` | `Instant.now().toString()` |\n\n### Add your own tokens\n\nEdit `~/.config/Harness/harness-settings.json`:\n\n```json\n{\n  \"customTokens\": [\n    {\n      \"name\": \"myAccount\",\n      \"label\": \"My test account\",\n      \"desc\": \"Cycles through 3 fixed account numbers\",\n      \"js\": \"['ACC-100','ACC-200','ACC-300'][Math.floor(Math.random()*3)]\",\n      \"java\": \"java.util.List.of(\\\"ACC-100\\\",\\\"ACC-200\\\",\\\"ACC-300\\\").get(java.util.concurrent.ThreadLocalRandom.current().nextInt(3))\"\n    }\n  ]\n}\n```\n\n`js` runs at replay time and is also embedded verbatim into exported\nJavaScript scripts. `java` is embedded verbatim into Selenium-Java\nexports. Either field is optional. Custom tokens show up at the top\nof the picker on next dialog open.\n\n## Selector-only export\n\nSome teams keep a separate object-repository file. Open a saved\nsession → **Export selectors** → pick CSV / JSON / YAML / XML.\n\nExample CSV:\n\n```\nname,selector\nusername_input,#username\npassword_input,#password\nsign_in_button,button[type=\"submit\"]\n```\n\nNames are derived from the locator label, name, aria-label,\nplaceholder, text, id, or data-testid, then suffixed with the action\nkind. Duplicates get numeric suffixes.\n\n## LLM prompt export\n\nFor frameworks Harness doesn't ship (WebdriverIO, TestCafe, Robot,\nCucumber, Appium, k6, custom in-house runners), record the flow then\n**Export ▾ → Build LLM prompt…**. Pick:\n\n- **Target framework** — Playwright, Cypress, Selenium, WebdriverIO, TestCafe, Robot, k6, Cucumber + WebDriver, Appium, or Custom (free-text description)\n- **Language** — JavaScript, TypeScript, Java, Python, C#, Ruby, Go\n- **Target LLM** — Claude, ChatGPT, Gemini, or generic\n- **Where will this run?** — *Add to an existing test suite* (LLM stays in lane, single test file, no package.json edits) or *Generate a fresh standalone project* (full scaffold: install commands, config, folder layout, runnable test file)\n- **Extra notes** — free-text guidance (Page Object Model, parameterise these values, target staging only, etc)\n\nClick **Copy to clipboard** or **Save as .txt**. The prompt looks like:\n\n```\nYou are Claude. Reason carefully but keep the final code production-quality, idiomatic, and minimal.\nYou are also a senior test automation specialist.\n\n# Task\nConvert the recorded user flow below into a runnable **Playwright** test, written in **TypeScript**.\n\n## Requirements\n- Use the exact selectors provided. Prefer the most stable form (id / data-testid / role) when there are alternatives.\n- Treat any value that looks like sample data (emails, names, account numbers) as a parameter…\n- Add explicit waits for visibility / network idle where appropriate; do not insert blind sleeps unless the recorded flow had a wait step.\n- For shadow DOM selectors written as `host \u003e\u003e child`, translate them into the framework's pierce syntax…\n- Wrap the whole flow in a single test or describe block named after the recorded URL.\n- Output only the final code in a fenced block, then a 2-3 line explanation. No marketing.\n\n## Recorded flow\n1. Navigate to `https://example.com/login`.\n2. Fill \"Email\" (selector: `#email`) with the value `alice@example.com`.\n3. Fill \"Password\" (selector: `#password`) with the value `hunter2`.\n4. Click on \"Sign in\" (selector: `button[type='submit']`).\n5. Assert that selector `.welcome` is visible.\n\nReturn the test now.\n```\n\nPaste straight into your LLM. Same recording, infinite frameworks.\n\n## Inspector\n\nInspect mode opens a live browser session with no recording. Right-click\nany element on the page. The side panel shows:\n\n- the picked selector (with shadow DOM `\u003e\u003e` chain when relevant)\n- a full attribute table including `id`, classes, `role`, `type`,\n  `checked` / `disabled` / `readonly`, computed visibility, value,\n  text content, bounding rect and every other DOM attribute\n\nUseful for verifying selectors before adding them to a script, or\nconstructing more reliable locators by hand.\n\n## Replay\n\nHit **Replay**. Harness wipes browser state, navigates to the recorded\nURL fresh, then walks every step. Each action scrolls the target into\nview, highlights it, fires the real input events. Pass/fail shows up\nnext to each step in the side panel.\n\nReplay also runs after recording stops without rebooting the session,\nso you can record then immediately verify.\n\n## Folders\n\nDrag a session row onto a folder chip. Drop on **Unfiled** to clear\nthe folder. Click **+ New folder** to add one. Filter by clicking a\nchip. The recent-sessions list scrolls when it grows beyond the\nsession config panel.\n\n## Tests\n\n```bash\nnpm test\n```\n\n61+ unit and integration tests across recorder, replay engine, codegen\n(all five targets), and locator builder. Run on every commit.\n\n## License\n\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolstackhq%2Fharness","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoolstackhq%2Fharness","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoolstackhq%2Fharness/lists"}