wopee-mcp 1.30.2 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -237,6 +237,11 @@ Generates a specific file(artifact) for the selected suite.
237
237
  - **Parameters:**
238
238
  - `suiteUuid` - The UUID of the suite
239
239
  - `type` - `"APP_CONTEXT" | "GENERAL_USER_STORIES" | "USER_STORIES_WITH_TEST_CASES" | "TEST_CASES" | "TEST_CASE_STEPS" | "REUSABLE_TEST_CASES" | "REUSABLE_TEST_CASE_STEPS"`
240
+ - `mode` _(optional, `TEST_CASES` only)_ - `"UPDATE"` (default) keeps existing test cases with their steps and run history; `"CREATE"` replaces the test cases in scope and deletes their steps, runs and code
241
+ - `selectedUserStories` _(optional)_ - user story ids to limit `TEST_CASES` / `TEST_CASE_STEPS` to, e.g. `["US001"]`; empty means every story
242
+ - `selectedTestCases` _(optional)_ - test case identifiers to limit `TEST_CASE_STEPS` to, e.g. `["US001:TC002"]`
243
+ - `instructions` _(optional)_ - the change request passed to the AI engine
244
+ - `async` _(optional, `TEST_CASES` / `USER_STORIES_WITH_TEST_CASES`)_ - return as soon as the run is queued instead of waiting for the artifact
240
245
  - **Returns:** Generated output in case of successful generation.
241
246
 
242
247
  **Example Usage:**
@@ -344,6 +349,26 @@ Fetch test results for suite <suiteUuid>
344
349
  Show me the executed test cases for my latest analysis suite
345
350
  ```
346
351
 
352
+ ### Reporting
353
+
354
+ #### `wopee_create_github_issue`
355
+
356
+ Files a GitHub issue into the repository linked to the current project. Given a failed run's `executedTestCaseUuid`, Wopee.io adds what it knows about that run below your text: the failing step, expected vs actual, the failing step's screenshot and a link to the run in Commander.
357
+
358
+ - **Parameters:**
359
+ - `title` - The issue title
360
+ - `body` _(optional)_ - Markdown, kept verbatim above the run evidence
361
+ - `labels` _(optional)_ - Labels to apply, e.g. `["bug"]`. GitHub may skip labels the repository doesn't have; the result says which were applied
362
+ - `executedTestCaseUuid` _(optional)_ - The run to attach evidence from, as shown by `wopee_fetch_recent_executions` or `wopee_dispatch_agent`
363
+ - **Returns:** The issue number and URL, plus notes on anything left out (e.g. a screenshot that could not be uploaded)
364
+ - **Requirements:** The issue is created by the Wopee.io GitHub App and names you in its body. The App's installation needs the **Issues: Read & write** repository permission, and **Contents: Read & write** for the screenshot, which is committed to a separate `wopee-issue-assets` branch. That branch has no shared history with your code and is safe to delete; deleting it only breaks the images in existing issues.
365
+
366
+ **Example Usage:**
367
+
368
+ ```
369
+ File the failed checkout test as a GitHub issue
370
+ ```
371
+
347
372
  ## Typical Workflow
348
373
 
349
374
  1. **Start with a suite:**
@@ -369,6 +394,9 @@ Show me the executed test cases for my latest analysis suite
369
394
  - Use `wopee_fetch_executed_test_cases` to check detailed status and reports for a specific suite
370
395
  - Or use the `fetch-test-results` prompt for a formatted summary of all test results
371
396
 
397
+ 6. **Report failures:**
398
+ - Use `wopee_create_github_issue` with the failed run's `executedTestCaseUuid` to file it into your repository
399
+
372
400
  ## Available Prompts
373
401
 
374
402
  ### `fetch-project-summary`
@@ -1,4 +1,5 @@
1
1
  import { getConfig } from "../../utils/getConfig.js";
2
+ import { generationOptions } from "../wopee_generate_artifact/request.js";
2
3
  export const createFetchArtifactInput = (input) => {
3
4
  const { WOPEE_PROJECT_UUID } = getConfig();
4
5
  if (!WOPEE_PROJECT_UUID)
@@ -27,10 +28,8 @@ export const createGenerateAIDataInput = (input) => {
27
28
  additionalVariables: null,
28
29
  },
29
30
  continueGeneration: false,
30
- extraPrompt: null,
31
- selectedUserStories: null,
32
- selectedTestCases: null,
33
31
  sourceSuiteUuid: null,
32
+ ...generationOptions(input),
34
33
  };
35
34
  };
36
35
  export const createUpdateArtifactInput = (input) => {
@@ -295,11 +295,26 @@ export const FetchRecentExecutedTestCases = `
295
295
  }
296
296
  `;
297
297
  export const CreateGitHubIssue = `
298
- mutation CreateGitHubIssue($projectUuid: ID!, $title: String!, $body: String!, $labels: [String!]) {
299
- createGitHubIssue(projectUuid: $projectUuid, title: $title, body: $body, labels: $labels) {
298
+ mutation CreateGitHubIssue(
299
+ $projectUuid: ID!
300
+ $title: String!
301
+ $body: String!
302
+ $labels: [String!]
303
+ $executedTestCaseUuid: ID
304
+ ) {
305
+ createGitHubIssue(
306
+ projectUuid: $projectUuid
307
+ title: $title
308
+ body: $body
309
+ labels: $labels
310
+ executedTestCaseUuid: $executedTestCaseUuid
311
+ ) {
300
312
  url
301
313
  number
302
314
  title
315
+ screenshotAttached
316
+ labelsApplied
317
+ warnings
303
318
  }
304
319
  }
305
320
  `;
@@ -1,6 +1,7 @@
1
1
  import { FetchArtifactInputSchema, UpdateArtifactInputSchema, GenerateAIDataInputSchema, } from "./schemas.js";
2
2
  import { FetchArtifact, UpdateArtifact } from "./gql-queries.js";
3
3
  import { createFetchArtifactInput, createUpdateArtifactInput, createGenerateAIDataInput, } from "./factories.js";
4
+ import { isQueuedGeneration } from "../wopee_generate_artifact/request.js";
4
5
  import { requestClient } from "../../utils/requestClient.js";
5
6
  import { _compactArtifactContentForModel, _convertToArtifactType, _parseError, _parseGenerateArtifactType, } from "./helpers.js";
6
7
  export async function fetchArtifact(input) {
@@ -63,6 +64,15 @@ export async function generateAIDataFile(input) {
63
64
  },
64
65
  ],
65
66
  };
67
+ if (isQueuedGeneration(input))
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: `Queued: ${description}. It runs in the background and can take several minutes; read the result with wopee_fetch_artifact once it has finished.`,
73
+ },
74
+ ],
75
+ };
66
76
  const convertedType = _convertToArtifactType(input.type);
67
77
  return await fetchArtifact({
68
78
  suiteUuid: parsedInput.suiteUuid,
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { ArtifactType, GenerateArtifactType } from "./types.js";
2
+ import { ArtifactType, GenerateArtifactType, TestCaseGenerationMode, } from "./types.js";
3
3
  var CookiesPreference;
4
4
  (function (CookiesPreference) {
5
5
  CookiesPreference["ACCEPT_ALL"] = "ACCEPT_ALL";
@@ -35,6 +35,31 @@ export const GenerateAIDataHandlerInputSchema = z.object({
35
35
  description: "UUID of the analysis suite to generate artifacts for. Get this from wopee_create_blank_suite or wopee_fetch_analysis_suites.",
36
36
  })
37
37
  .min(1, "Suite UUID is required"),
38
+ mode: z
39
+ .nativeEnum(TestCaseGenerationMode, {
40
+ description: "TEST_CASES only. UPDATE (default) keeps every existing test case with its steps and run history, and adds or changes test cases around them. CREATE replaces the test cases of the stories in scope — of the whole suite when selectedUserStories is empty — and deletes their steps, run history and Playwright code. Use CREATE only when the user explicitly asks to start over.",
41
+ })
42
+ .default(TestCaseGenerationMode.UPDATE),
43
+ selectedUserStories: z
44
+ .array(z.string(), {
45
+ description: "User story ids to limit the generation to, e.g. ['US001', 'US003']. Leave empty for every story in the suite. Used by TEST_CASES and TEST_CASE_STEPS.",
46
+ })
47
+ .optional(),
48
+ selectedTestCases: z
49
+ .array(z.string(), {
50
+ description: "Test case identifiers to limit TEST_CASE_STEPS to, e.g. ['US001:TC002']. Leave empty for every test case in scope. Regenerating steps replaces the steps those test cases have.",
51
+ })
52
+ .optional(),
53
+ instructions: z
54
+ .string({
55
+ description: "What the user asked for, in their words, e.g. 'add a negative test for an expired card'. Passed to the AI engine as the change request; without it, UPDATE removes no test case.",
56
+ })
57
+ .optional(),
58
+ async: z
59
+ .boolean({
60
+ description: "TEST_CASES and USER_STORIES_WITH_TEST_CASES only. true returns as soon as the generation is queued instead of waiting for it (a large suite can take several minutes); the content is then read later with wopee_fetch_artifact. Default false: wait and return the generated artifact.",
61
+ })
62
+ .optional(),
38
63
  });
39
64
  export const GenerateAIDataInputSchema = z.object({
40
65
  projectUuid: z.string().min(1, "Project UUID is required"),
@@ -45,6 +70,8 @@ export const GenerateAIDataInputSchema = z.object({
45
70
  selectedTestCases: z.array(z.string()).nullish(), // ["US001:TC001", "US003:TC002", "US015:TC033"]
46
71
  suiteAnalysisConfig: SuiteAnalysisConfigSchema,
47
72
  continueGeneration: z.boolean().nullish().default(false),
73
+ mode: z.nativeEnum(TestCaseGenerationMode).nullish(),
74
+ async: z.boolean().nullish(),
48
75
  });
49
76
  export const FetchArtifactHandlerInputSchema = z.object({
50
77
  type: z.nativeEnum(ArtifactType, {
@@ -31,6 +31,11 @@ export var GenerateArtifactType;
31
31
  GenerateArtifactType["REUSABLE_TEST_CASES"] = "REUSABLE_TEST_CASES";
32
32
  GenerateArtifactType["REUSABLE_TEST_CASE_STEPS"] = "REUSABLE_TEST_CASE_STEPS";
33
33
  })(GenerateArtifactType || (GenerateArtifactType = {}));
34
+ export var TestCaseGenerationMode;
35
+ (function (TestCaseGenerationMode) {
36
+ TestCaseGenerationMode["UPDATE"] = "UPDATE";
37
+ TestCaseGenerationMode["CREATE"] = "CREATE";
38
+ })(TestCaseGenerationMode || (TestCaseGenerationMode = {}));
34
39
  export var ArtifactType;
35
40
  (function (ArtifactType) {
36
41
  ArtifactType["APP_CONTEXT"] = "APP_CONTEXT";
@@ -0,0 +1,21 @@
1
+ // The text an MCP caller reads back after filing an issue.
2
+ //
3
+ // No runtime imports, which keeps it unit-testable under `node --test`.
4
+ export function formatIssueCreated(issue, { withRunEvidence }) {
5
+ const lines = [
6
+ "GitHub issue created successfully:",
7
+ `- Title: ${issue.title}`,
8
+ `- Number: #${issue.number}`,
9
+ `- URL: ${issue.url}`,
10
+ ];
11
+ if (issue.labelsApplied?.length) {
12
+ lines.push(`- Labels: ${issue.labelsApplied.join(", ")}`);
13
+ }
14
+ if (withRunEvidence) {
15
+ lines.push(`- Run evidence: failing step, expected vs actual and the Commander report link added; screenshot ${issue.screenshotAttached ? "attached" : "not attached"}`);
16
+ }
17
+ for (const warning of issue.warnings ?? []) {
18
+ lines.push(`- Note: ${warning}`);
19
+ }
20
+ return lines.join("\n");
21
+ }
@@ -4,21 +4,27 @@ import { requestClient } from "../../utils/requestClient.js";
4
4
  import { _parseError } from "../shared/helpers.js";
5
5
  import { ToolName } from "../shared/types.js";
6
6
  import { CreateGitHubIssue } from "../shared/gql-queries.js";
7
+ import { formatIssueCreated } from "./format.js";
7
8
  const InputSchema = z.object({
8
9
  title: z.string().describe("The title of the GitHub issue"),
9
10
  body: z
10
11
  .string()
11
- .describe("The body/description of the GitHub issue (supports Markdown)"),
12
+ .optional()
13
+ .describe("The body/description of the GitHub issue (supports Markdown). Kept verbatim, above anything Wopee.io adds from the test run."),
12
14
  labels: z
13
15
  .array(z.string())
14
16
  .optional()
15
17
  .describe("Optional labels to apply to the issue (e.g., ['bug', 'testing'])"),
18
+ executedTestCaseUuid: z
19
+ .string()
20
+ .optional()
21
+ .describe("UUID of the executed test case the issue is about (the executedTestCaseUuid shown by wopee_fetch_recent_executions and wopee_dispatch_agent, or uuid in wopee_fetch_executed_test_cases). When set, Wopee.io adds the failing step, expected vs actual, the failing step's screenshot and a link to the run in Commander, so the body only needs your own analysis."),
16
22
  });
17
23
  export const wopeeCreateGithubIssue = {
18
24
  name: ToolName.WOPEE_CREATE_GITHUB_ISSUE,
19
25
  config: {
20
26
  title: "Create GitHub issue",
21
- description: "Create a new GitHub issue in the project's connected repository. Use this to report bugs found during testing, track test failures, or create action items from chat discussions. The issue will be created in the GitHub repository linked to the current project. Requires the project to have GitHub integration configured and WOPEE_PROJECT_UUID to be set.",
27
+ description: "Create a new GitHub issue in the project's connected repository. Use this to report bugs found during testing, track test failures, or create action items from chat discussions. For a failed test, pass its executedTestCaseUuid so the issue carries the run's evidence (failing step, expected vs actual, screenshot, report link). The issue is created by the Wopee.io GitHub App and names the requester in its body. Requires a GitHub repository linked to the project and WOPEE_PROJECT_UUID to be set.",
22
28
  inputSchema: InputSchema.shape,
23
29
  },
24
30
  handler: async (input) => {
@@ -33,15 +39,18 @@ export const wopeeCreateGithubIssue = {
33
39
  const result = await requestClient(CreateGitHubIssue, {
34
40
  projectUuid: WOPEE_PROJECT_UUID,
35
41
  title: input.title,
36
- body: input.body,
42
+ body: input.body ?? "",
37
43
  labels: input.labels || [],
44
+ ...(input.executedTestCaseUuid && {
45
+ executedTestCaseUuid: input.executedTestCaseUuid,
46
+ }),
38
47
  });
39
48
  if (!result?.createGitHubIssue)
40
49
  return {
41
50
  content: [
42
51
  {
43
52
  type: "text",
44
- text: "Failed to create GitHub issue. Make sure the project has GitHub integration configured.",
53
+ text: "Failed to create GitHub issue. Make sure the project has a GitHub repository linked.",
45
54
  },
46
55
  ],
47
56
  };
@@ -49,7 +58,9 @@ export const wopeeCreateGithubIssue = {
49
58
  content: [
50
59
  {
51
60
  type: "text",
52
- text: `GitHub issue created successfully:\n- Title: ${result.createGitHubIssue.title}\n- Number: #${result.createGitHubIssue.number}\n- URL: ${result.createGitHubIssue.url}`,
61
+ text: formatIssueCreated(result.createGitHubIssue, {
62
+ withRunEvidence: !!input.executedTestCaseUuid,
63
+ }),
53
64
  },
54
65
  ],
55
66
  };
@@ -52,6 +52,8 @@ export const wopeeFetchRecentExecutions = {
52
52
  // INCOMPLETE on top, and reporting it as FAILED tells the user their app is broken.
53
53
  const status = tc.agentReportStatus ?? tc.executionStatus;
54
54
  lines.push(`- ${tc.userStoryId}:${tc.testCaseId} [${tc.analysisIdentifier}] → ${status} (${tc.updatedAt})`);
55
+ // The handle wopee_create_github_issue takes to attach this run's evidence.
56
+ lines.push(` executedTestCaseUuid: ${tc.uuid}`);
55
57
  // Any report, not only a FINISHED run's: an incomplete run's report is what names the
56
58
  // infrastructure error behind it.
57
59
  if (tc.agentReport) {
@@ -5,7 +5,7 @@ export const wopeeGenerateArtifact = {
5
5
  name: ToolName.WOPEE_GENERATE_ARTIFACT,
6
6
  config: {
7
7
  title: "Generate test artifacts",
8
- description: "Generate AI-powered test artifacts for a suite using the Wopee.io AI engine. Each call creates one artifact type — call multiple times for different types. Generation order matters: APP_CONTEXT must be generated before user stories, and user stories before test cases. If called out of order, the AI may produce lower quality results. On success, returns confirmation that generation started. Use wopee_fetch_artifact to retrieve the generated content once ready. Do NOT use this to update existing artifacts — use wopee_update_artifact instead. Generating the same type again overwrites the previous version.",
8
+ description: "Generate AI-powered test artifacts for a suite using the Wopee.io AI engine. Each call creates one artifact type — call multiple times for different types. Generation order matters: APP_CONTEXT must be generated before user stories, and user stories before test cases. If called out of order, the AI may produce lower quality results. On success, returns confirmation that generation started. Use wopee_fetch_artifact to retrieve the generated content once ready. Do NOT use this to hand-edit existing artifacts — use wopee_update_artifact instead. TEST_CASES runs in UPDATE mode by default: existing test cases keep their steps and run history, and `instructions` says what to add or change. Destructive generations are mode CREATE (replaces the test cases in scope and deletes their steps, runs and code), USER_STORIES_WITH_TEST_CASES (rebuilds every story and test case) and TEST_CASE_STEPS (replaces the steps of the test cases in scope): use them only when the user explicitly asks for that, and scope them with selectedUserStories / selectedTestCases when the user named stories or test cases.",
9
9
  inputSchema: GenerateAIDataHandlerInputSchema.shape,
10
10
  },
11
11
  handler: async (input) => await generateAIDataFile(input),
@@ -0,0 +1,19 @@
1
+ const TEST_CASES = "TEST_CASES";
2
+ /** The generations the api can queue and answer at once; every other one always waits. */
3
+ const QUEUEABLE_TYPES = [
4
+ TEST_CASES,
5
+ "USER_STORIES_WITH_TEST_CASES",
6
+ ];
7
+ /** Whether this request is answered as soon as the api has queued it. */
8
+ export const isQueuedGeneration = (input) => input.async === true && QUEUEABLE_TYPES.includes(input.type);
9
+ /** An empty scope means every story or test case, which the api reads as null. */
10
+ const scope = (ids) => ids && ids.length > 0 ? ids : null;
11
+ export const generationOptions = (input) => ({
12
+ extraPrompt: input.instructions?.trim() || null,
13
+ selectedUserStories: scope(input.selectedUserStories),
14
+ selectedTestCases: scope(input.selectedTestCases),
15
+ // Sent on every test case generation, never left to the api's default: CREATE is the one mode
16
+ // that deletes work, and an older api treats a missing mode as CREATE.
17
+ ...(input.type === TEST_CASES ? { mode: input.mode } : {}),
18
+ ...(isQueuedGeneration(input) ? { async: true } : {}),
19
+ });
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "bin": {
5
5
  "wopee-mcp": "./build/index.js"
6
6
  },
7
- "version": "1.30.2",
7
+ "version": "1.32.0",
8
8
  "mcpName": "io.github.Wopee-io/wopee-mcp",
9
9
  "description": "Wopee.io MCP server for autonomous testing platform",
10
10
  "main": "./build/index.js",