Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Read the [migration guide](docs/MIGRATING_TO_GO.md) for installation, command di

[Small UI change. Big test drama. Explore the developer memes on 9lives.run →](https://9lives.run/#memes)

**Linux. macOS. Windows. One local workflow.** The [Go runner v0.3.0](https://github.com/Quality-Max/9lives-runner/releases/tag/v0.3.0) has native binaries for all three, on x64 and ARM64. Keep your Playwright project, configuration, and assertions across your team’s machines. Windows users: follow the [ZIP download and checksum guide](https://github.com/Quality-Max/9lives-runner/blob/main/docs/install.md). Project dependencies and Chromium are separate; Windows provider CLI shell integrations remain unqualified.
**Linux. macOS. Windows. One local workflow.** The [Go runner v0.4.0](https://github.com/Quality-Max/9lives-runner/releases/tag/v0.4.0) has native binaries for all three, on x64 and ARM64. Keep your Playwright project, configuration, and assertions across your team’s machines. Windows users: follow the [ZIP download and checksum guide](https://github.com/Quality-Max/9lives-runner/blob/main/docs/install.md). Project dependencies and Chromium are separate; Windows provider CLI shell integrations remain unqualified.

## Recommended: Go runner for Playwright

Expand All @@ -33,14 +33,14 @@ The installer uses a pinned, checksum-verified release and installs into a separ
Alternatively, download a [prebuilt release](https://github.com/Quality-Max/9lives-runner/releases), or with Go 1.25.13 or newer:

```bash
go install github.com/Quality-Max/9lives-runner/cmd/9l@v0.1.1
go install github.com/Quality-Max/9lives-runner/cmd/9l@v0.4.0
# Add your Go binary directory to PATH, then confirm which CLI you selected:
9l --version
9l plan tests/
9l run tests/ --timeout 5m --format json
```

Go requires an existing Playwright project with locally installed tooling and browsers. It provides planning, bounded execution, cancellation, and validated receipts. The optional SDK adds steps and bounded goals; see the [runner documentation](https://github.com/Quality-Max/9lives-runner/blob/main/docs/README.md). Go v0.3 has experimental native `9l heal` and native `9l mcp`. The website installer is pinned to v0.1.1, whose `heal` command delegates to this Python package; use the release downloads for v0.3 features.
Go requires an existing Playwright project with locally installed tooling and browsers. It provides planning, bounded execution, cancellation, and validated receipts. The optional SDK adds steps and bounded goals; see the [runner documentation](https://github.com/Quality-Max/9lives-runner/blob/main/docs/README.md). Go v0.4 has experimental native `9l heal`, native `9l mcp`, and the `9l scan` and `9l inspect` page checks. The website installer installs v0.4.0.

## Existing Python workflows

Expand Down Expand Up @@ -119,7 +119,7 @@ claude mcp add 9lives -- uvx --from 9lives 9lives mcp

The behavior-vs-drift guard applies to agents too: a failing assertion comes back as `needs-human`, with an explicit note that forcing it green would mask a real bug. `heal_test` only accepts existing test files under the directory `9lives mcp` was started in (`NINELIVES_MCP_UNRESTRICTED=1` lifts this), since healing a spec ultimately executes it.

Healing is half the loop — the spec has to come from somewhere. 9lives' sibling MCP server, [qmax-mcp](https://github.com/Quality-Max/qmax-mcp) (`npx -y @qualitymax/qmax-mcp`, MIT), covers the other half: scan a page for defects, inspect it for stability-ranked locators and a testability score, generate a Playwright repro, and run it. Same rules — local, free, no account. A spec born on qmax-mcp's stability-ranked locators is exactly the kind 9lives can keep alive when it drifts.
Healing is half the loop — the spec has to come from somewhere. The Go runner now covers the other half, with the checks that used to be the separate qmax-mcp server: `9l scan <url>` finds console, link, accessibility, Core Web Vitals, SEO, security-header, cookie, mixed-content and page-weight problems from one page load; `9l inspect <url>` ranks a page's locators by stability; and `9l mcp` gives coding agents `scan_url`, `inspect_page` and `generate_playwright_repro` beside its run, heal, assess and confirm tools. Same rules — local, free, no account. A spec built on stable locators is exactly the kind 9lives can keep alive when it drifts. See [Check the page you changed](https://github.com/Quality-Max/9lives-runner#check-the-page-you-changed).

## Heal on save & pre-commit

Expand Down
13 changes: 1 addition & 12 deletions docs/marketing/meme-assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Created with the built-in image generation tool, using the approved Product Hunt thumbnail as the brand reference: fluffy black Qmax kitten, violet collar, cyan terminal tag, ivory background, and bold indigo typography. These are illustrated developer jokes, not product screenshots or benchmarks.

Production assets are 1024 × 1024 WebP at quality 84. The gallery uses explicit dimensions, lazy loading, descriptive alt text, and links to the full images. The comparison meme spans both desktop columns; mobile uses one column.
Production assets are 1024 × 1024 WebP at quality 84. The gallery uses explicit dimensions, lazy loading, descriptive alt text, and links to the full images.

Hosted on **9lives.run only**. QualityMax's homepage has no meme gallery. The GitHub README links to the gallery.

Expand All @@ -12,9 +12,6 @@ Hosted on **9lives.run only**. QualityMax's homepage has no meme gallery. The Gi
| harry-potter | `website/public/memes/harry-potter-launch.webp` |
| lotr | `website/public/memes/lotr-launch.webp` |
| batman-catwoman | `website/public/memes/batman-catwoman-launch.webp` |
| distracted-team | `website/public/memes/distracted-team-launch.webp` |

The comparison uses the official e2e logo from [TesterArmy's repository](https://github.com/tester-army/e2e/blob/main/docs/logo-black.png), retrieved on 2026-10-11. It identifies e2e by TesterArmy. The middle character is a Playwright developer in glasses and a hoodie carrying a laptop. The composition is an original cat parody of the distracted-boyfriend meme; it does not reuse the photograph.

The older non-launch assets remain hosted to preserve existing social and email URLs. The email PNG is unchanged.

Expand All @@ -37,11 +34,3 @@ Epic fantasy cat parody. Headline "ONE DOES NOT SIMPLY". Central black kitten we
### batman-catwoman

Original feline superhero parody. Headline "THE BUTTON MOVED." Two panels. Left: fluffy black kitten wearing homemade bat-eared dark cape, exhausted at red FAIL laptop, caption "ANOTHER LONG NIGHT". Right: original black kitten with violet collar and cyan tag, tiny violet eye mask and flowing violet cape, confident beside green PASS laptop, caption "NINE LIVES". Bottom "Keep the assertion. Verify the fix." Footer "9lives.run". Original cats only, no official superhero symbols or human likenesses.

### distracted-team

Use case: compositing.
Edit target: image 1, the square distracted-boyfriend cat meme. Supporting insert: image 2 is the authentic official e2e logo.
Make the middle grey cat visibly a software developer: add neat thin dark glasses, a charcoal hoodie, and a slim closed laptop tucked under one front paw; preserve its turning pose and gaze toward the black 9lives kitten. Change the middle label from "Playwright team" to "Playwright developer" on two lines.
Use the official e2e logo from image 2 above the ginger cat at right, faithfully preserving its "/ e2e" shape, dark color, and proportions. Under the logo put "by TesterArmy" as one or two lines of large readable indigo text. Remove the previous "e2e by TesterArmy" text so there is no duplicate e2e.
Keep the left Qmax black kitten, violet collar, cyan terminal tag, fur, pose, face, label "9lives" unchanged. Keep the ivory street scene, square composition, heading "WHEN YOU ALREADY HAVE PLAYWRIGHT TESTS", bottom "Keep the tests. Repair the locator." and "9lives.run" unchanged. Match the original Qmax launch style. Text must be readable at small sizes; no extra words, metrics, logos or watermark.
7 changes: 4 additions & 3 deletions install/9l.run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
# Python compatibility: pip install 9lives / uv tool install 9lives.
set -eu

VERSION="${NINELIVES_VERSION:-v0.1.1}"
VERSION="${NINELIVES_VERSION:-v0.4.0}"
INSTALL_DIR="${NINELIVES_INSTALL_DIR:-$HOME/.local/share/9lives-runner/bin}"
REPOSITORY="https://github.com/Quality-Max/9lives-runner"

fail() { printf '9lives: %s\n' "$1" >&2; exit 1; }
say() { printf '9lives: %s\n' "$1"; }

case "$VERSION" in
*[!v0-9.]*) fail "NINELIVES_VERSION must be a stable tag such as v0.1.1" ;;
*[!v0-9.]*) fail "NINELIVES_VERSION must be a stable tag such as v0.4.0" ;;
esac
printf '%s\n' "$VERSION" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' ||
fail "NINELIVES_VERSION must be a stable tag such as v0.1.1"
fail "NINELIVES_VERSION must be a stable tag such as v0.4.0"
case "$INSTALL_DIR" in
/*) ;;
*) fail "NINELIVES_INSTALL_DIR must be an absolute directory path" ;;
Expand Down Expand Up @@ -109,6 +109,7 @@ say "add this directory to PATH, then check '9l --version' selects the Go runner
quoted_dir="$(printf '%s' "$INSTALL_DIR" | sed "s/'/'\\\\''/g")"
printf " export PATH='%s':\"\$PATH\"\n" "$quoted_dir"
say "from an existing Playwright project: 9l plan tests/ && 9l run tests/"
say "check the page you changed: 9l scan https://your-app.dev --format markdown"
say "install project dependencies and Playwright browsers separately"
say "guide: https://quality-max.github.io/9lives-runner/"
say "Python compatibility remains available via '9lives' or 'python -m ninelives.cli'"
18 changes: 17 additions & 1 deletion tests/test_website.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def test_landing_page_keeps_install_and_ecosystem_links():
assert "https://github.com/Quality-Max/9lives-runner" in page
assert page.count('href="https://qualitymax.io"') == 2
for destination in (
"https://qmax.run",
"https://aiinqa.com/ai-in-qa-issue-18/",
"https://github.com/Quality-Max/qmax-code",
"https://github.com/Quality-Max/qualitymax-grader",
Expand All @@ -25,6 +24,23 @@ def test_landing_page_keeps_install_and_ecosystem_links():
assert destination in page


def test_landing_page_presents_the_page_checks_built_into_the_runner():
page = (WEBSITE / "app" / "page.tsx").read_text()

assert "https://qmax.run" not in page
assert "9l scan https://your-app.dev --format markdown" in page
assert "claude mcp add 9lives -- 9l mcp" in page
for tool in ("scan_url", "inspect_page", "generate_playwright_repro", "confirm_finding"):
assert tool in page


def test_installer_defaults_to_the_release_with_page_checks():
installer = (ROOT / "install" / "9l.run.sh").read_text()

assert 'VERSION="${NINELIVES_VERSION:-v0.4.0}"' in installer
assert "9l scan" in installer


def test_site_uses_the_original_qualitymax_favicon():
layout = (WEBSITE / "app" / "layout.tsx").read_text()

Expand Down
16 changes: 11 additions & 5 deletions website/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ code { font-family: var(--font-geist-mono), monospace; }
.hero-note a, .compatibility-copy a { color: var(--cyan); text-decoration: underline; text-underline-offset: 3px; }
.compatibility-copy { max-width: 820px; color: var(--muted); line-height: 1.7; }
#python .inline-command { margin: 24px 0; }
.command-stack .command-card:last-child { grid-column: 1 / -1; }
.command-stack .command-card:last-child:nth-child(odd) { grid-column: 1 / -1; }
.featured-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 20px; }
.featured-link { display: inline-flex; align-items: center; gap: 5px; padding: 8px 12px; border: 1px solid rgba(85, 230, 255, .18); border-radius: 999px; color: #9f9ab5; background: rgba(85, 230, 255, .04); font-size: 12px; transition: border-color .2s, color .2s, background .2s; }
.featured-link strong { color: #dbd7ea; }
Expand All @@ -95,7 +95,6 @@ code { font-family: var(--font-geist-mono), monospace; }
.meme-section { padding-top: 24px; }
.meme-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.meme-card { margin: 0; overflow: hidden; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); }
.meme-card:first-child { grid-column: 1 / -1; width: 100%; max-width: 760px; justify-self: center; }
.meme-card > a { display: block; }
.meme-card > a:focus-visible { outline: 3px solid var(--cyan); outline-offset: -4px; }
.meme-card img { display: block; width: 100%; height: auto; aspect-ratio: 1; }
Expand All @@ -121,8 +120,7 @@ code { font-family: var(--font-geist-mono), monospace; }

.ecosystem-section { padding-bottom: 120px; }
.ecosystem-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 14px; }
.ecosystem-card { position: relative; grid-column: span 2; min-height: 230px; padding: 24px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); transition: border-color .2s, translate .2s, background .2s; }
.ecosystem-card:nth-child(4) { grid-column: 2 / span 2; }
.ecosystem-card { position: relative; grid-column: span 3; min-height: 230px; padding: 24px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); transition: border-color .2s, translate .2s, background .2s; }
.ecosystem-card:hover, .ecosystem-card:focus-visible { translate: 0 -4px; border-color: rgba(157, 124, 255, .55); background: var(--surface-2); outline: none; }
.ecosystem-card > span { color: var(--violet); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; }
.ecosystem-card h3 { margin: 28px 0 10px; font-size: 22px; }
Expand Down Expand Up @@ -161,7 +159,7 @@ footer .legal-note { grid-column: 1 / -1; }
.step-card { min-height: 210px; }
.step-card h3 { margin-top: 42px; }
.ecosystem-grid { grid-template-columns: 1fr; }
.ecosystem-card, .ecosystem-card:nth-child(4) { grid-column: auto; }
.ecosystem-card { grid-column: auto; }
.framework-strip { gap: 12px; font-size: 10px; letter-spacing: .08em; }
footer { grid-template-columns: 1fr; justify-items: center; }
footer p, footer > span { text-align: center; }
Expand All @@ -180,3 +178,11 @@ footer .legal-note { grid-column: 1 / -1; }
.feature-grid h3 { font-size: 20px; margin: 0 0 10px; }
.feature-grid p { color: var(--muted); line-height: 1.7; margin: 0; }
@media (max-width: 760px) { .feature-grid { grid-template-columns: 1fr; } }
#page-checks .step-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
#page-checks .step-card h3 { font-size: 20px; overflow-wrap: anywhere; }
#page-checks .feature-grid { margin: 48px 0 40px; }
#page-checks .feature-grid h3 { font-size: 17px; }
#page-checks .inline-command { margin-bottom: 24px; }
@media (max-width: 1080px) { #page-checks .step-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 760px) { #page-checks .step-grid { grid-template-columns: 1fr; } }

15 changes: 15 additions & 0 deletions website/app/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('Go runner onboarding', () => {
expect(screen.getAllByText('curl -fsSL https://9lives.run/install.sh | sh')).toHaveLength(2);
expect(screen.getByText('export PATH="$HOME/.local/share/9lives-runner/bin:$PATH"')).toBeTruthy();
expect(screen.getByText('9l run tests/ --timeout 5m --format json')).toBeTruthy();
expect(screen.getByText('9l scan https://your-app.dev --format markdown')).toBeTruthy();
expect(screen.getByRole('link', { name: 'Documentation' }).getAttribute('href'))
.toBe('https://quality-max.github.io/9lives-runner/');
expect(screen.getByRole('link', { name: /github/i }).getAttribute('href'))
Expand All @@ -62,3 +63,17 @@ describe('Go runner onboarding', () => {
.toBe('https://github.com/Quality-Max/9lives/blob/main/docs/MIGRATING_TO_GO.md');
});
});

describe('Page checks', () => {
it('presents the verification loop and the nine checks built into the runner', () => {
render(<Home />);

expect(screen.getByRole('heading', { name: 'From “looks done” to evidence it works.' })).toBeTruthy();
for (const check of ['console', 'links', 'accessibility', 'performance', 'seo', 'security_headers', 'cookies', 'mixed_content', 'weight']) {
expect(screen.getByRole('heading', { name: check })).toBeTruthy();
}
expect(screen.getByText('claude mcp add 9lives -- 9l mcp')).toBeTruthy();
expect(screen.getByRole('link', { name: 'Page checks' }).getAttribute('href')).toBe('#page-checks');
expect(screen.queryAllByRole('link').some((link) => link.getAttribute('href')?.startsWith('https://qmax.run'))).toBe(false);
});
});
Loading
Loading