Skill: testdino-inc/testdino-playwright (version 1.0.2)

--- name: testdino-openclaw description: Connect OpenClaw to TestDino for real-time Playwright CI intelligence. Ask about test failures, flaky tests, run history, and CI health in plain English. homepage: https://github.com/testdino-hq/TestDino-OpenClaw-skills metadata: {"openclaw": {"os": ["linux", "darwin", "win32"], "primaryEnv": "TESTDINO_PAT", "requires": {"bins": ["mcporter", "testdino-mcp"], "env": ["TESTDINO_PAT"]}, "install": [{"id": "npm-mcporter", "kind": "npm", "package": "mcporter", "bins": ["mcporter"], "label": "Install mcporter"}, {"id": "npm-testdino-mcp", "kind": "npm", "package": "testdino-mcp", "bins": ["testdino-mcp"], "label": "Install TestDino MCP"}]}} --- # TestDino — Playwright CI Intelligence Use the `exec` tool to call TestDino. Always use `mcporter`: ``` mcporter call testdino.<tool> [params] ``` **Important:** Always call `health` first to get the `projectId` unless the user has already provided it. Every other tool requires it. --- ## Commands (copy these exactly) **Check connection / health check:** ``` exec: mcporter call testdino.health ``` **Project summary / CI overview / "summarize project X":** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testruns projectId=X by_time_interval=1d limit=5 ``` Use the returned run IDs to fetch details if the user wants more depth. **Show failures / failed tests (any time range):** Resolve `TIME` from the user's words using the time mapping below, then run: ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testcase projectId=X by_status=failed by_time_interval=TIME limit=10 ``` The response includes `totalCount` — always use that as the total failure count. **Show failures on a branch / any failures on branch Y? / new failures on branch Y?:** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testcase projectId=X by_branch=BRANCH_NAME by_status=failed limit=10 ``` **CI summary / how did CI look / test run stats (any time range):** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testruns projectId=X by_time_interval=TIME ``` **Show recent test runs:** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testruns projectId=X limit=10 ``` ⚠️ **How to read list_testruns response:** - The `testRuns` array is the complete flat list — use it directly, ordered newest first - Each run has a `counter` field — always use this as the run number (e.g. "Run #10") - Show ALL runs from `testRuns`, never skip any - `rerunMetadata.isRerun: true` means this run is a retry — note it as "🔁 Retry of #[parentCounter]" **Why did test run [ID or counter] fail? / Details for a specific test run:** If the user provides a test run ID (e.g. [REDACTED_SECRET]): ``` exec: mcporter call testdino.health exec: mcporter call testdino.get_run_details projectId=X testrun_id=test_run_XXX exec: mcporter call testdino.list_testcase projectId=X by_testrun_id=test_run_XXX by_status=failed limit=10 ``` If the user provides a counter number (e.g. run #42): ``` exec: mcporter call testdino.health exec: mcporter call testdino.get_run_details projectId=X counter=N exec: mcporter call testdino.list_testcase projectId=X counter=N by_status=failed limit=10 ``` **Why is [test name] failing? / Debug [test]:** If the user did not provide a test name, ask: "Which test case name would you like to debug?" and wait for their answer before running anything. Once you have the real test name, ALWAYS run these commands — even if a previous attempt failed. Never give generic advice instead of running the tool: ``` exec: mcporter call testdino.health exec: mcporter call testdino.debug_testcase projectId=X testcase_name=EXACT TEST NAME WITHOUT QUOTES ``` **Show flaky tests / is [test] flaky? / is [test] flaky or a real bug?:** If user names a specific test → use `debug_testcase` (it returns flakiness patterns). If user asks generally → use: ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testcase projectId=X by_status=flaky by_time_interval=TIME limit=10 ``` **Is it safe to merge branch Y?:** If no branch name was given, ask: "Which branch should I check?" and wait before running. ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testcase projectId=X by_branch=Y by_status=failed limit=10 ``` **Show timeout failures:** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testcase projectId=X by_error_category=timeout_issues by_time_interval=TIME limit=10 ``` **List manual test cases / total count / show [priority] test cases / show [type] test cases:** ⚠️ Use `list_manual_test_cases` — NOT `list_testcase`. These are completely different tools. ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_manual_test_cases projectId=X limit=1000 ``` ⚠️ **IMPORTANT — verify the count:** After the response comes back, check the `count` field. If `count` is exactly 10, the API used its default limit and you do NOT have all results. You MUST retry with `limit=1000`: ``` exec: mcporter call testdino.list_manual_test_cases projectId=X limit=1000 ``` Only trust the `count` from a response where you explicitly passed `limit=1000`. Add filters if user specifies: - `priority=critical` / `priority=high` / `priority=medium` / `priority=low` - `type=smoke` / `type=regression` / `type=functional` - `status=active` / `status=draft` Examples: - "critical priority test cases" → `list_manual_test_cases projectId=X priority=critical limit=1000` - "smoke test cases" → `list_manual_test_cases projectId=X type=smoke limit=1000` **If user asks for total count:** Read the `count` field from the response and reply with just the number. **If user asks to list/show:** Show only caseId + title. Show the first 15, then ALWAYS end with: _"There are more test cases available. Let me know if you'd like to see more!"_ **List test suites / show all suites:** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_manual_test_suites projectId=X ``` Show only the `name` field from each suite. Show the first 15 suites, then end with: _"There are more suites available. Let me know if you'd like to see more!"_ NEVER show: suite IDs, metadata, hierarchy, timestamps, descriptions (they are empty), or tags (they don't exist in the response). NEVER invent test case counts or test case names — `list_manual_test_suites` does NOT return test cases. **List spec files / show spec files / all spec files in project:** ``` exec: mcporter call testdino.health exec: mcporter call testdino.list_testruns projectId=X limit=1 exec: mcporter call testdino.get_run_details projectId=X counter=N ``` From the `get_run_details` response, extract `testSuites[].fileName` and list only the unique spec file names. --- ## Missing parameters - No projectId → run health first to get it, unless user already provided it - No test name → ask: "Which test case name?" - No time range → ask: "Today (1d), 3 days (3d), 7 days, or monthly?" - Test run ID vs counter → `testrun_id` accepts the full string ID; `counter` accepts a sequential number (e.g. `42`) ### Time range mapping (apply strictly before every query) | User says | TIME value | |---|---| | today / right now / no time mentioned | `1d` | | yesterday / last day | `1d` | | last 2 days / 2 days | `3d` | | last 3 days / 3 days | `3d` | | last 5 days / 5 days | `3d` | | this week / last week / 7 days / last 7 days / weekly | compute 7-days-ago and today as `YYYY-MM-DD,YYYY-MM-DD` e.g. `2026-03-23,2026-03-30` — do NOT use the `weekly` keyword (it has an exclusive boundary that drops the oldest day) | | last 2 weeks / 14 days | `monthly` | | this month / last month / 30 days / monthly | `monthly` | | specific date e.g. "27 march 2026" / "27-03-2026" / "2026-03-27" | `2026-03-27,2026-03-28` (next day as end date) | | date range e.g. "20 march to 25 march" | `2026-03-20,2026-03-25` | Always normalize any date format the user gives to `YYYY-MM-DD`. For a specific single day, use `YYYY-MM-DD,YYYY-MM-DD+1` (end date = next day). For "last N days" not in the table, use `3d` if under 7 days, a computed date range (`YYYY-MM-DD,YYYY-MM-DD`) if exactly 7 days, `monthly` if over 7. --- ## Response format **CRITICAL: NEVER invent or fabricate data.** Only show fields that exist in the actual API response. If a field is missing or empty, do not guess or fill it in. If the response is too large to process fully, show what you can and say "there are more — ask if you'd like to see more." Never make up descriptions, tags, test counts, or test case names that are not in the response. **Keep responses short. Always use `totalCount` from the response for the count.** **For list_testruns — use this exact format:** ``` **[ProjectName]** — [N] runs - Run #[counter] — [Mon DD] | [branch] | ✅ [passed] passed ❌ [failed] failed[ | 🔁 Retry of #[parentCounter]] - Run #[counter] — ... ``` NEVER include: run IDs, start times, durations, environment, author, status, total tests, skipped/flaky/timedOut counts. **For list_testcase (failures) — use this exact format:** ``` **[ProjectName]** — **[totalCount] failures** **Run #[counter]** 1. [test title] 2. [test title] _(showing N of totalCount — ask for more if needed)_ ``` ONLY show run number and test title. NEVER show test case IDs, run IDs, durations, status, start times, spec file paths. - Merge safety: lead with yes/no then failure count - Debug: 2-3 sentences on dominant pattern, note if flaky - Show full details only if the user specifically asks

path: README.md
content: # TestDino for OpenClaw

Stop checking CI dashboards. TestDino brings your Playwright test intelligence
into OpenClaw so failures, flaky tests, and CI trends come to you via Slack or chat.

Ask your assistant things like:
- "What broke in CI today?"
- "Why is the payment test failing?"
- "Is the login test flaky or a real bug?"
- "Is it safe to merge the release branch?"
- "Give me a weekly CI health summary"

---

## Requirements

- [OpenClaw](https://openclaw.dev) installed and running
- [TestDino account](https://app.testdino.com) with a Personal Access Token
- Node.js 18+
- [mcporter](https://www.npmjs.com/package/mcporter) (CLI tool for running MCP servers)

---

## Setup

### Step 1: Get your TestDino PAT

1. Log in to [app.testdino.com](https://app.testdino.com)
2. Go to **Settings → Personal Access Tokens**
3. Generate a new token and copy it

---

### Step 2: Install mcporter and testdino-mcp globally

```bash
npm install -g mcporter
npm install -g testdino-mcp
```

---

### Step 3: Configure mcporter

Create or edit `~/.mcporter/mcporter.json`:

```json
{
  "mcpServers": {
    "testdino": {
      "command": "testdino-mcp",
      "env": {
        "TESTDINO_PAT": "your-personal-access-token"
      }
    }
  },
  "imports": []
}
```

**Verify mcporter works before continuing:**

```bash
mcporter call testdino.health
```

You should see your account name and list of projects. If this fails, fix it before moving on — if mcporter doesn't work, the bot won't work either.

---

### Step 4: Configure openclaw.json

Open `~/.openclaw/openclaw.json` and add the `env` block:

```json
{
  "env": {
    "TESTDINO_PAT": "your-personal-access-token"
  }
}
```

Also make sure your `openclaw.json` has the `tools` profile set to `coding` and the skill entry:

```json
{
  "tools": {
    "profile": "coding"
  },
  "skills": {
    "entries": {
      "testdino": {
        "apiKey": "your-personal-access-token"
      }
    }
  }
}
```

> The `coding` tools profile is required — it enables the `exec` tool that the bot uses to call mcporter.

---

### Step 5: Install the skill

**Option A — manually (works now):**

Copy `SKILL.md` from this repo into your OpenClaw workspace skills folder:

```
~/.openclaw/workspace/skills/testdino/SKILL.md
```

Create the folder if it doesn't exist.

**Option B — via ClawHub (coming soon):**

```bash
clawhub install testdino
```

---

### Step 6: Add TestDino to your TOOLS.md

Create or open `~/.openclaw/workspace/TOOLS.md` and add this section at the bottom:

```markdown
## TestDino — Playwright CI Intelligence

For TestDino queries, always use the `exec` tool with `mcporter`. Always run `health` first to get the projectId, then substitute it for `X`.

| User asks | Commands to run |
|---|---|
| Check connection | `exec: mcporter call testdino.health` |
| What broke today | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_testcase projectId=X by_status=failed by_time_interval=1d` |
| Recent test runs | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_testruns projectId=X limit=10` |
| Why is [test] failing | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.debug_testcase projectId=X testcase_name="name"` |
| Flaky tests | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_testcase projectId=X by_status=flaky by_time_interval=3d` |
| Safe to merge branch Y? | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_testcase projectId=X by_branch=Y by_status=failed` |
| Weekly CI summary | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_testruns projectId=X by_time_interval=YYYY-MM-DD,YYYY-MM-DD` (use date range: 7 days ago to today — avoid the `weekly` keyword which has an exclusive boundary bug) |
| Timeout failures | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_testcase projectId=X by_error_category=timeout_issues` |
| Manual test cases | `exec: mcporter call testdino.health` then `exec: mcporter call testdino.list_manual_test_cases projectId=X` |

Never use invented commands like `testdino check-connection` or `testdino.get_ci_status`. Only use the commands above.
```

> This step is required. Without it, the bot won't know how to call TestDino tools on a fresh session.

---

### Step 7: Restart OpenClaw and verify

```bash
openclaw gateway --force
```

Then ask your bot:

```
Check my TestDino connection
```

**First-time exec approval:** On the first run, OpenClaw will pause and ask you to approve `mcporter` as an allowed exec command. Approve it — this only happens once. After that, all TestDino commands run automatically without prompting.

You should see your account name and available projects.

---

## How to Use

Once set up, just ask in plain English. Examples:

**Check connection**
```
Check my TestDino connection
```

**CI Failures**
```
What broke in CI today?
Show me failures on the develop branch
List all timeout failures from this week
Show failed tests in the last 3 days
```

**Flaky Test Analysis**
```
Is the checkout test flaky?
Why is "Verify user login" failing?
Show me all flaky tests this week
```

**Merge Safety**
```
Is it safe to merge the auth-refactor branch?
Any new failures on the feature/cart branch?
```

**CI Summaries**
```
Give me a weekly CI summary
How did CI look yesterday?
Show test run stats for the last month
```

**Manual Test Cases**
```
List all manual test cases
Show me critical priority test cases
```

---

## Automated Alerts and Digests (Cron)

> **Note:** Crons only fire while the OpenClaw gateway is running. For reliable delivery, keep the gateway running as a background process or service. If you close the gateway, no cron messages will be sent until it is restarted.

There are three ways to set up cron jobs. Choose whichever fits your workflow.

---

### Method 1 — CLI (`openclaw cron add`)

The recommended approach. Run these commands while the gateway is running.

> **Before running — two things to prepare:**
>
> **1. Your gateway token** — find it in `~/.openclaw/openclaw.json` under `gateway.auth.token`. Pass it via `--token`.
>
> **2. Your delivery destination** — replace `YOUR_CHANNEL` and `YOUR_DESTINATION` based on your configured channel:
>
> | Channel | `--channel` | `--to` format | How to find your ID |
> |---|---|---|---|
> | Slack | `slack` | `channel:CXXXXXXXXX` | Right-click channel in Slack → View channel details → Copy channel ID |
> | Slack DM | `slack` | `UXXXXXXXXX` | Slack profile → More → Copy member ID |
> | Telegram | `telegram` | your Telegram chat ID | Message @userinfobot on Telegram |
> | Discord | `discord` | your Discord channel ID | Right-click channel in Discord → Copy Channel ID |
> | WhatsApp | `whatsapp` | phone in E.164 format | e.g. `+15551234567` |

**Morning CI Digest:**
```bash
openclaw cron add --name "testdino-morning-digest" --cron "0 9 * * 1-5" --session isolated --announce --channel YOUR_CHANNEL --to "YOUR_DESTINATION" --token "your-gateway-token" --message "Call the TestDino health tool to get my project ID. Then call list_testruns with by_time_interval=1d to get all runs from the last 24 hours. For the most recent run, call get_run_details to get full stats. Then call list_testcase with by_status=failed and by_time_interval=1d to get failed tests, and list_testcase with by_status=flaky and by_time_interval=1d to get flaky tests. Format as a morning digest: total runs, pass rate, failures (names + error categories), flaky test count. Keep it short and scannable."
```

**Failure Alerts:**
```bash
openclaw cron add --name "testdino-failure-watch" --every 15m --session isolated --announce --channel YOUR_CHANNEL --to "YOUR_DESTINATION" --token "your-gateway-token" --message "Call the TestDino health tool to get my project ID. Then call list_testcase with by_status=failed and by_time_interval=1h. Look only at test cases that appear recent (within the last 15 minutes based on timestamps if available). If there are any failed tests, send an alert listing: the test names, the branch they are on, and the error category. If there are zero failures, do not send any message — stay completely silent."
```

Verify they were created:
```bash
openclaw cron list --token "your-gateway-token"
```

---

### Method 2 — Direct JSON (`~/.openclaw/cron/jobs.json`)

Write directly to the cron store. Create or edit `~/.openclaw/cron/jobs.json`:

```json
{
  "jobs": [
    {
      "id": "testdino-morning-digest-01",
      "name": "testdino-morning-digest",
      "enabled": true,
      "schedule": { "kind": "cron", "expr": "0 9 * * 1-5" },
      "sessionTarget": "isolated",
      "wakeMode": "now",
      "payload": {
        "kind": "agentTurn",
        "message": "Call the TestDino health tool to get my project ID. Then call list_testruns with by_time_interval=1d to get all runs from the last 24 hours. For the most recent run, call get_run_details to get full stats. Then call list_testcase with by_status=failed and by_time_interval=1d to get failed tests, and list_testcase with by_status=flaky and by_time_interval=1d to get flaky tests. Format as a morning digest: total runs, pass rate, failures (names + error categories), flaky test count. Keep it short and scannable."
      },
      "delivery": { "mode": "announce", "channel": "YOUR_CHANNEL", "to": "YOUR_DESTINATION" }
    },
    {
      "id": "testdino-failure-watch-01",
      "name": "testdino-failure-watch",
      "enabled": true,
      "schedule": { "kind": "every", "everyMs": 900000 },
      "sessionTarget": "isolated",
      "wakeMode": "now",
      "payload": {
        "kind": "agentTurn",
        "message": "Call the TestDino health tool to get my project ID. Then call list_testcase with by_status=failed and by_time_interval=1h. Look only at test cases that appear recent (within the last 15 minutes based on timestamps if available). If there are any failed tests, send an alert listing: the test names, the branch they are on, and the error category. If there are zero failures, do not send any message — stay completely silent."
      },
      "delivery": { "mode": "announce", "channel": "YOUR_CHANNEL", "to": "YOUR_DESTINATION" }
    }
  ]
}
```

Then restart the gateway to pick up the changes:
```bash
openclaw gateway --force
```

---

### Method 3 — `openclaw.json` (future support)

> **Version note:** Support for `crons` in `openclaw.json` depends on your OpenClaw version. Not available in **2026.3.13** — if your gateway reports `Unrecognized key: "crons"` on startup, use Method 1 or Method 2 instead.

```json
{
  "crons": [
    {
      "name": "testdino-morning-digest",
      "schedule": "0 9 * * 1-5",
      "sessionTarget": "isolated",
      "prompt": "Call the TestDino health tool to get my project ID. Then call list_testruns with by_time_interval=1d to get all runs from the last 24 hours. For the most recent run, call get_run_details to get full stats. Then call list_testcase with by_status=failed and by_time_interval=1d to get failed tests, and list_testcase with by_status=flaky and by_time_interval=1d to get flaky tests. Format as a morning digest: total runs, pass rate, failures (names + error categories), flaky test count. Keep it short and scannable."
    },
    {
      "name": "testdino-failure-watch",
      "schedule": "*/15 * * * *",
      "sessionTarget": "isolated",
      "prompt": "Call the TestDino health tool to get my project ID. Then call list_testcase with by_status=failed and by_time_interval=1h. Look only at test cases that appear recent (within the last 15 minutes based on timestamps if available). If there are any failed tests, send an alert listing: the test names, the branch they are on, and the error category. If there are zero failures, do not send any message — stay completely silent."
    }
  ]
}
```

---

See the [`examples/`](./examples/) folder for more cron templates.

---

## Available Tools

| Tool | What it does |
|---|---|
| `health` | Validate PAT, get project IDs |
| `list_testruns` | Browse test runs with filters |
| `get_run_details` | Full details for a specific run |
| `list_testcase` | Filter test cases by status, branch, browser, error type |
| `get_testcase_details` | Error messages, stack traces, logs, artifacts |
| `debug_testcase` | Historical failure patterns + flaky detection |
| `list_manual_test_cases` | Search manual test cases |
| `get_manual_test_case` | Details for a specific manual test case |
| `create_manual_test_case` | Create a new manual test case |
| `update_manual_test_case` | Update an existing manual test case |
| `list_manual_test_suites` | List suite hierarchy |
| `create_manual_test_suite` | Create a new test suite |

---

## Troubleshooting

**"Error validating PAT" in the bot**
- Run `mcporter call testdino.health` in your terminal first. If that fails, the PAT in `~/.mcporter/mcporter.json` is wrong.
- If terminal works but bot fails, make sure `testdino-mcp` is installed globally (`npm install -g testdino-mcp`).

**Bot gives generic responses instead of TestDino data**
- Make sure you completed Step 6 (adding TestDino to `TOOLS.md`) — this is the most commonly missed step.
- Make sure `tools.profile` is set to `coding` in `openclaw.json` — without it the `exec` tool is unavailable.
- Restart the gateway: `openclaw gateway --force`
- Make sure `SKILL.md` is in `~/.openclaw/workspace/skills/testdino/SKILL.md`

**Bot asks for permission every time it runs mcporter**
- On the first run, approve `mcporter` in the exec prompt. It should not ask again after that.
- If it keeps asking, check your `exec-approvals.json` at `~/.openclaw/exec-approvals.json` — mcporter should appear in the allowlist.

**Config invalid / unrecognized key errors on gateway start**
- Do not add an `mcp` top-level key to `openclaw.json` — OpenClaw does not support it
- Run `openclaw doctor --fix` to clean up any invalid keys

---

## About TestDino

TestDino is a Playwright test intelligence platform. It tracks CI runs, classifies
failures by error category, detects flaky tests through historical pattern analysis,
and gives you detailed debugging data across every test execution.

- Website: [testdino.com](https://testdino.com)
- Docs: [docs.testdino.com](https://docs.testdino.com)
- Support: [REDACTED_SECRET]


path: examples/morning-digest.md
content: # Morning Digest — Daily CI Health Summary

Sends a test health digest every weekday at 9am to your configured OpenClaw
notification channel (Telegram, Slack, Discord, etc.).

## What it covers

- Total CI runs in the last 24 hours
- New failures vs the day before
- Flaky test count and trend
- Top failing tests by name and error category
- Any tests that recovered (passed after previously failing)

---

## Setup

### Method 1 — CLI (recommended)

Run this while the gateway is running. Replace `YOUR_CHANNEL`, `YOUR_DESTINATION`, and `your-gateway-token` with your values — see the [main README](../README.md#method-1--cli-openclaw-cron-add) for how to find them.

```bash
openclaw cron add --name "testdino-morning-digest" --cron "0 9 * * 1-5" --session isolated --announce --channel YOUR_CHANNEL --to "YOUR_DESTINATION" --token "your-gateway-token" --message "Call the TestDino health tool to get my project ID. Then call list_testruns with by_time_interval=1d to get all runs from the last 24 hours. For the most recent run, call get_run_details to get full stats. Then call list_testcase with by_status=failed and by_time_interval=1d to get failed tests, and list_testcase with by_status=flaky and by_time_interval=1d to get flaky tests. Format as a morning digest: (1) Run summary — total runs, pass rate; (2) Failures — test names and error categories; (3) Flaky tests — count and names; (4) One-line health status. Keep it short."
```

### Method 2 — Direct JSON (`~/.openclaw/cron/jobs.json`)

Add this entry to your `~/.openclaw/cron/jobs.json` then restart the gateway:

```json
{
  "id": "testdino-morning-digest-01",
  "name": "testdino-morning-digest",
  "enabled": true,
  "schedule": { "kind": "cron", "expr": "0 9 * * 1-5" },
  "sessionTarget": "isolated",
  "wakeMode": "now",
  "payload": {
    "kind": "agentTurn",
    "message": "Call the TestDino health tool to get my project ID. Then call list_testruns with by_time_interval=1d to get all runs from the last 24 hours. For the most recent run, call get_run_details to get full stats. Then call list_testcase with by_status=failed and by_time_interval=1d to get failed tests, and list_testcase with by_status=flaky and by_time_interval=1d to get flaky tests. Format as a morning digest: (1) Run summary — total runs, pass rate; (2) Failures — test names and error categories; (3) Flaky tests — count and names; (4) One-line health status. Keep it short."
  },
  "delivery": { "mode": "announce", "channel": "YOUR_CHANNEL", "to": "YOUR_DESTINATION" }
}
```

### Method 3 — `openclaw.json`

> **Version note:** Support for `crons` in `openclaw.json` depends on your OpenClaw version. Not available in **2026.3.13** — if your gateway reports `Unrecognized key: "crons"` on startup, use Method 1 or Method 2 instead.

```json
{
  "crons": [
    {
      "name": "testdino-morning-digest",
      "schedule": "0 9 * * 1-5",
      "sessionTarget": "isolated",
      "prompt": "Call the TestDino health tool to get my project ID. Then call list_testruns with by_time_interval=1d to get all runs from the last 24 hours. For the most recent run, call get_run_details to get full stats. Then call list_testcase with by_status=failed and by_time_interval=1d to get failed tests, and list_testcase with by_status=flaky and by_time_interval=1d to get flaky tests. Format as a morning digest: (1) Run summary — total runs, pass rate; (2) Failures — test names and error categories; (3) Flaky tests — count and names; (4) One-line health status. Keep it short."
    }
  ]
}
```

---

## Schedule Options

Change the `--cron` value to match your timezone or preference:

| `--cron` value | Meaning |
|---|---|
| `0 9 * * 1-5` | 9am weekdays |
| `0 8 * * 1-5` | 8am weekdays |
| `0 9 * * *` | 9am every day including weekends |
| `0 6 * * 1-5` | 6am weekdays (early alert) |

---

## Example Output

```
TestDino Morning Digest — Monday

Runs: 4 runs in the last 24 hours. Pass rate: 91%.

Failures (3):
• checkout.spec.ts — assertion_failure — main branch
• payment-flow.spec.ts — timeout_issues — main branch
• user-auth.spec.ts — element_not_found — develop branch

Flaky (2):
• sidebar-nav.spec.ts
• modal-close.spec.ts

Recoveries (1):
• login-redirect.spec.ts — passing again after 2 days

Overall: 3 real failures need attention. 2 known flaky tests still noisy.
```


path: examples/alert-setup.md
content: # Failure Alerts — Real-Time CI Failure Notifications

Polls TestDino every 15 minutes and sends an alert when new failures appear.
Stays silent when CI is clean or only flaky tests are failing.

## What triggers an alert

- Any test with `status=failed` in the last 15 minutes
- Only sends a message if failures are found — no noise when everything passes

## What the alert includes

- Test name
- Branch name
- Error category (timeout, element_not_found, assertion_failure, network_issues)
- Number of failures found

---

## Setup

### Method 1 — CLI (recommended)

Run this while the gateway is running. Replace `YOUR_CHANNEL`, `YOUR_DESTINATION`, and `your-gateway-token` with your values — see the [main README](../README.md#method-1--cli-openclaw-cron-add) for how to find them.

```bash
openclaw cron add --name "testdino-failure-watch" --every 15m --session isolated --announce --channel YOUR_CHANNEL --to "YOUR_DESTINATION" --token "your-gateway-token" --message "Call the TestDino health tool to get my project ID. Then call list_testcase with by_status=failed and by_time_interval=1h. Look only at test cases that appear recent (within the last 15 minutes based on timestamps if available). If there are any failed tests, send an alert listing: the test names, the branch they are on, and the error category. If there are zero failures, do not send any message — stay completely silent."
```

### Method 2 — Direct JSON (`~/.openclaw/cron/jobs.json`)

Add this entry to your `~/.openclaw/cron/jobs.json` then restart the gateway:

```json
{
  "id": "testdino-failure-watch-01",
  "name": "testdino-failure-watch",
  "enabled": true,
  "schedule": { "kind": "every", "everyMs": 900000 },
  "sessionTarget": "isolated",
  "wakeMode": "now",
  "payload": {
    "kind": "agentTurn",
    "message": "Call the TestDino health tool to get my project ID. Then call list_testcase with by_status=failed and by_time_interval=1h. Look only at test cases that appear recent (within the last 15 minutes based on timestamps if available). If there are any failed tests, send an alert listing: the test names, the branch they are on, and the error category. If there are zero failures, do not send any message — stay completely silent."
  },
  "delivery": { "mode": "announce", "channel": "YOUR_CHANNEL", "to": "YOUR_DESTINATION" }
}
```

### Method 3 — `openclaw.json`

> **Version note:** Support for `crons` in `openclaw.json` depends on your OpenClaw version. Not available in **2026.3.13** — if your gateway reports `Unrecognized key: "crons"` on startup, use Method 1 or Method 2 instead.

```json
{
  "crons": [
    {
      "name": "testdino-failure-watch",
      "schedule": "*/15 * * * *",
      "sessionTarget": "isolated",
      "prompt": "Call the TestDino health tool to get my project ID. Then call list_testcase with by_status=failed and by_time_interval=1h. Look only at test cases that appear recent (within the last 15 minutes based on timestamps if available). If there are any failed tests, send an alert listing: the test names, the branch they are on, and the error category. If there are zero failures, do not send any message — stay completely silent."
    }
  ]
}
```

---

## Alert Frequency Options

| Schedule | Meaning |
|---|---|
| `--every 15m` | Every 15 minutes (recommended) |
| `--every 10m` | Every 10 minutes |
| `--every 30m` | Every 30 minutes |
| `--cron "0 * * * *"` | Once per hour |
| `--cron "*/5 * * * 1-5"` | Every 5 minutes on weekdays only |

---

## Combining Digest + Alerts

Run both together — the digest gives you the morning overview, the alerts catch new failures during the day. See [morning-digest.md](./morning-digest.md) for the digest setup, then add both crons.

---

## Example Alert Output

```
TestDino Alert — New CI Failure

2 failures detected:

• checkout.spec.ts — assertion_failure — branch: main
• payment-flow.spec.ts — timeout_issues — branch: main
```

---

## Tips

- If you get too many alerts during a known flaky period, temporarily change
  the frequency to `--every 1h` until things stabilize.
- To monitor a specific branch only, add `on branch X` to the prompt.
- To suppress alerts outside working hours, use `--cron "*/15 8-18 * * 1-5"` (every 15 min, 8am–6pm weekdays).


path: scripts/install-check.sh
content: #!/usr/bin/env bash
# install-check.sh
# Verifies that the testdino-mcp package is reachable and your PAT is valid.
# Run this after setting TESTDINO_PAT to confirm the skill will work.
#
# Usage:
#   export TESTDINO_PAT=your-pat-here
#   bash scripts/install-check.sh

set -e

echo ""
echo "TestDino OpenClaw Skill — Install Check"
echo "======================================="
echo ""

# 1. Check Node.js
echo "Checking Node.js..."
if ! command -v node &>/dev/null; then
  echo "FAIL: Node.js not found. Install Node.js 18+ from https://nodejs.org"
  exit 1
fi

NODE_VERSION=$(node --version)
NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1 | tr -d 'v')
if [ "$NODE_MAJOR" -lt 18 ]; then
  echo "FAIL: Node.js $NODE_VERSION found. Version 18 or higher is required."
  exit 1
fi
echo "  OK: Node.js $NODE_VERSION"

# 2. Check npx
echo "Checking npx..."
if ! command -v npx &>/dev/null; then
  echo "FAIL: npx not found. Install Node.js 18+ which includes npx."
  exit 1
fi
echo "  OK: npx found"

# 3. Check TESTDINO_PAT
echo "Checking TESTDINO_PAT..."
if [ -z "$TESTDINO_PAT" ]; then
  echo "FAIL: TESTDINO_PAT environment variable is not set."
  echo ""
  echo "  Set it with:"
  echo "    export TESTDINO_PAT=your-personal-access-token"
  echo ""
  echo "  Get your PAT at: https://app.testdino.com → Settings → Personal Access Tokens"
  exit 1
fi

PAT_LENGTH=${#TESTDINO_PAT}
echo "  OK: TESTDINO_PAT is set (${PAT_LENGTH} characters)"

# 4. Check testdino-mcp package reachability
echo "Checking testdino-mcp package..."
if ! npx --yes testdino-mcp --version &>/dev/null 2>&1; then
  # Some versions may not support --version, try a different check
  if ! npm show testdino-mcp version &>/dev/null 2>&1; then
    echo "WARN: Could not verify testdino-mcp package. Check your internet connection."
    echo "      The package should be available at: https://www.npmjs.com/package/testdino-mcp"
  else
    MCP_VERSION=$(npm show testdino-mcp version 2>/dev/null)
    echo "  OK: testdino-mcp@${MCP_VERSION} available on npm"
  fi
else
  echo "  OK: testdino-mcp reachable via npx"
fi

# 5. Validate PAT against TestDino API
echo "Validating PAT with TestDino API..."
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $TESTDINO_PAT" \
  "https://api.testdino.com/api/mcp/hello" 2>/dev/null || echo "000")

if [ "$HTTP_STATUS" = "200" ]; then
  echo "  OK: PAT is valid — TestDino API responded successfully"
elif [ "$HTTP_STATUS" = "401" ]; then
  echo "FAIL: PAT is invalid or expired (HTTP 401)."
  echo "      Generate a new PAT at: https://app.testdino.com → Settings → Personal Access Tokens"
  exit 1
elif [ "$HTTP_STATUS" = "000" ]; then
  echo "WARN: Could not reach TestDino API. Check your internet connection."
  echo "      API endpoint: https://api.testdino.com"
else
  echo "WARN: Unexpected response from TestDino API (HTTP $HTTP_STATUS)."
fi

echo ""
echo "======================================="
echo "All checks passed. The testdino skill is ready."
echo ""
echo "Next steps:"
echo "  1. Install OpenClaw: https://openclaw.ai"
echo "  2. Run: clawhub install testdino"
echo "  3. Add TESTDINO_PAT to your openclaw.json env"
echo "  4. Ask: 'Check my TestDino connection'"
echo ""


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)