E2e Tests logo

E2e Tests

Organization
vstorm-co
e2e-tests

Write or fix a Playwright end-to-end test in frontend/e2e — a new journey spec, a seeded fixture, or debugging "the e2e suite passes but the feature is broken" / "make test-e2e fails". Use for any spec that crosses the whole stack (sign in, build an agent, publish, run, approve). Every page in this product renders its empty state when a query fails, so a spec that asserts on chrome is a spec that asserts on nothing.

Overview

Publishervstorm-co
Repositoryagenticos
Skill namee2e-tests
Stars
137
Forks
27
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by vstorm-co on GitHub. Read the source before you install it.

Installation

Install the E2e Tests AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/vstorm-co/agenticos/tree/main/.claude/skills/e2e-tests
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/vstorm-co/agenticos.git /tmp/agenticos
mkdir -p .claude/skills
cp -r /tmp/agenticos/.claude/skills/e2e-tests .claude/skills/e2e-tests
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable E2e Tests in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads E2e Tests on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so E2e Tests is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

E2E — the suite that once tested a dead backend

bash
make test-e2e          # checks a backend is up and says so if not
cd frontend && bun run test:e2e:ui        # pick and watch specs
bun run test:e2e:headed                   # see it happen

Playwright starts the frontend. The backend, its migrations and agenticos cmd bootstrap are the precondition.

The rule

A spec must assert on something the seed put there.

Every dashboard page is a client component that fans out to several queries, and every one renders its empty state when a query fails. "No skills yet" and "the skills request answered 502" are the same pixels. A spec that asserts a heading and a button passes against a backend that was never started — which is exactly how this suite spent months testing the login page.

Two consequences:

  • Assert on a seeded row — a name, a slug, a count.
  • Where a resource genuinely has no seeded row, assert on the response instead.

The net, which is not a substitute

test from e2e/fixtures.ts fails any test whose page took a 5xx from /api/*. Only 5xx: a 401 from a deliberately wrong password and a 403 from a refusal are the product working, and several specs exist to prove exactly that. /api/rag/* is exempt — a test deployment has no embedding key, so collection stats answer 500 by design.

Always import test and expect from ./fixtures, never from @playwright/test directly, or the spec loses the net.

Never assert a new row straight after a submit

Creating something through a dialog goes through submitDialog (e2e/helpers.ts).

click(submit) followed by expect(theNewRow).toBeVisible() sat at six sites, was seen to flake at four of them, and cost three separate diagnoses in one day (#132) — every time with the same message, element(s) not found, sixteen seconds later.

That message is the trap. An open Radix dialog takes the rest of the page out of the accessibility tree: while one is on screen getByRole("main"), getByRole("row") and skillCard() resolve to nothing at all. So the assertion could only ever report the absence of the page, and a create refused with a 409 looked identical to a list that had not refetched. Measured: main counts 1, then 0 while the dialog is open, then 1 again — in the same sample that first sees the row.

submitDialog waits on two signals rather than hopes: the write's own response, whose status and body are the diagnosis, and then the dialog closing — the app saying it has finished everything it does around the write. A longer expect timeout is not an alternative to either; it makes a race slower to fail.

It does not promise the row is rendered, and that is not a shortcut — the list's refetch is sometimes answered with the pre-write list even though the row is committed and both server layers return it (#230, about one run in eight). Two consequences:

  • A fixture step asks the API, and asks more than once. Every step of seed.setup.ts asserts through /api/… now, because its job is that the fixture exists. Whether it renders is a product claim, and vault.spec.ts / skills.spec.ts make it against the rows the seed left, on pages they loaded themselves. Asking the API is not enough on its own: a 2xx from this backend means the request was answered, not that the write is readable. The commit sits in a Depends-with-yield exit code, which FastAPI unwinds after the response has gone out (#353) — measured at 21.7ms on one acceptance. So a check that follows a write is nowThere, a poll, never a single read; the alreadyThere guard at the head of each step stays one read, because it runs before the write. The membership step was the one site #222 missed when it converted the file, and it cost 87 skipped specs three times in a day (#335). One step is still the exception in either direction: a draft agent exists waits on the Builder's heading.
  • A product spec about the rendering reloads first, marked #230, until that issue closes. A spec that made the write and then asserts on the row waits the same way the fixtures do: nowListed and nowMatching in e2e/helpers.ts are nowThere moved out of seed.setup.ts so both sides use one primitive (#162). Waiting first splits one failure into two — a failure in the poll is a write that never landed, a failure in the toBeVisible() after it is a page that did not draw a row the API was serving, and those are different defects that used to share element(s) not found.

The list's GET is deliberately not a third wait: useKnowledgeBases never makes one, and where one is made, #230 is about the answer being wrong rather than late.

It also ignores a 401 on the write's path. apiClient.send refreshes an expired access token and re-issues the same request, so the app acts on the retry — stopping at the first response would fail a write that succeeded.

A failing [seed] step runs no product spec at all

setup and seed are project dependencies. When a step in one fails, Playwright does not run what depends on it, and the log reads 1 failed, 7 passed, 17 did not run — which on a pull request looks like a product regression. e2e/fixture-reporter.ts prints a banner saying otherwise, and a GitHub error annotation under CI. Read it before touching product code, and treat a green re-run as evidence about the fixture rather than about the branch.

Prove a new spec can fail

Point BACKEND_URL at a dead port and watch it go red before you trust it. A spec that has never failed has never been tested.

The two fixture layers

agenticos cmd bootstrap — an owner, an organization, a model profile, and the published agent @getting-started. It walks a fresh install to one working agent and stops there on purpose; it is the shortest path to seeing the product work, not a demo database.

e2e/seed.setup.ts — what bootstrap deliberately does not: a skill, a knowledge base, a draft agent, a stored key, an org MCP connection, and a second member. It creates them through the UI, so the setup is itself a test of the create paths.

Names live in e2e/helpers.ts as constants (SEEDED_SKILL_NAME, DRAFT_AGENT_SLUG, …). Assert against those constants, never a literal — a renamed fixture then fails loudly in one place instead of in six specs.

Need a new fixture? Add it to seed.setup.ts and export its name from helpers.ts. Do not create data inside a spec unless creating it is the behaviour under test.

What belongs here, and what does not

E2E is for journeys crossing the whole system: sign in, build an agent, publish it, run it, approve a tool call, share a conversation.

It is not for: a permission hiding a button (that is a frontend integration test with a mocked API — cheaper and more precise), a service rule (unit), or a constraint (backend integration). See the frontend-feature and backend-tests skills.

Existing specs

auth, agents, chat, journey, models, skills, vault, sharing, navigation, sidebar, sidebar-active, activity, refusals, mcp-servers, kb-ingestion, kb-integrations, notifications. Read the closest one before adding a new file — refusals.spec.ts in particular, since asserting a refusal is where this suite earns its keep.

The model that answers is a stub, and that is why the journey runs

journey.spec.ts is the only spec that runs an agent end to end, and it needs a model to answer. It used to need a real provider key and skipped itself without one — which meant the one spec covering the seams ran in no environment, CI included.

e2e/stub-model-server.ts serves the Chat Completions API, including the SSE path the chat takes, and playwright.config.ts starts it beside the app on 127.0.0.1:4010. A model profile points at it through the Endpoint field, which model profiles allow on loopback deliberately: a local model is a first-class provider here.

Two things follow, and both are easy to get wrong:

  • The stub echoes the token it is told to say and generates nothing. That is the assertion: only the published agent's instructions can put that token in the request, so the reply proves the spec reached the provider. Do not teach it to be clever.
  • It returns usage, because the journey's last assertion is a cost. A stub with zero usage would make the spec pass on a run that metered nothing — the one thing it is there to catch.

What it does not cover, and should not pretend to: that a real provider answers, or that a real key is accepted. Those would make a green suite depend on somebody else's uptime.

The pgvector trap

The database must be pgvector/pgvector:pg16, not stock Postgres. The RAG store issues CREATE EXTENSION IF NOT EXISTS vector on first write, and stock Postgres answers extension "vector" is not available — a 500 before any row is committed. If an upload spec 500s on a fresh environment, check the image first. Every compose file and both CI jobs pin it; they used to pin postgres:16-alpine, which is why no ingestion path had ever been exercised and an upload spec sat skipped.

Frequently asked questions

What does the E2e Tests AI skill do?

Write or fix a Playwright end-to-end test in frontend/e2e — a new journey spec, a seeded fixture, or debugging "the e2e suite passes but the feature is broken" / "make test-e2e fails". Use for any spec that crosses the whole stack (sign in, build an agent, publish, run, approve). Every page in this product renders its empty state when a query fails, so a spec that asserts on chrome is a spec that asserts on nothing.

Why use E2e Tests on TypingMind?

Because you install it once and use it with any model. E2e Tests is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install E2e Tests in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/vstorm-co/agenticos/tree/main/.claude/skills/e2e-tests. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use E2e Tests?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with E2e Tests?

As many as you like. As long as a model supports skills, you can use E2e Tests with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the E2e Tests AI skill free?

Yes. It is published on GitHub by vstorm-co under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇