원클릭으로
e2e-test
Write E2E tests for Deenruv plugins using Vitest and @deenruv/testing utilities
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Write E2E tests for Deenruv plugins using Vitest and @deenruv/testing utilities
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
How to add, update, and manage i18n translations in Deenruv plugins and admin UI
Build admin UI components and pages using @deenruv/react-ui-devkit (shadcn/Tailwind-based)
Step-by-step guide to create a new Deenruv plugin with server and UI components
How to extend the Deenruv GraphQL API with new types, queries, mutations, and resolvers
SOC 직업 분류 기준
| name | e2e-test |
| description | Write E2E tests for Deenruv plugins using Vitest and @deenruv/testing utilities |
Use this skill when:
@deenruv/testingplugins/<feature>-plugin/
├── e2e/
│ ├── feature.e2e-spec.ts # E2E test file
│ ├── fixtures/ # Test fixtures (CSV, assets)
│ │ ├── e2e-products-minimal.csv
│ │ └── assets/
│ └── graphql/
│ └── generated-e2e-admin-types.ts # Generated types (optional)
└── package.json # Must have e2e script
e2e-common/
├── vitest.config.mts # Shared Vitest config (SWC for decorators)
├── test-config.ts # Auto-assigns unique port per suite (starts 3010)
├── e2e-initial-data.ts # Seed data: tax rates, shipping, countries, collections
└── tsconfig.e2e.json # TypeScript config for E2E
Every plugin with E2E tests needs these scripts:
{
"scripts": {
"test": "vitest --run",
"e2e": "cross-env PACKAGE=feature-plugin vitest --config ../../e2e-common/vitest.config.mts --run",
"e2e:watch": "cross-env PACKAGE=feature-plugin vitest --config ../../e2e-common/vitest.config.mts"
}
}
The shared vitest.config.mts includes SWC for NestJS decorator support and sets timeouts based on E2E_DEBUG.
// plugins/feature-plugin/e2e/feature.e2e-spec.ts
import { mergeConfig } from "@deenruv/core";
import { createTestEnvironment } from "@deenruv/testing";
import gql from "graphql-tag";
import path from "path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { initialData } from "../../../e2e-common/e2e-initial-data";
import {
testConfig,
TEST_SETUP_TIMEOUT_MS,
} from "../../../e2e-common/test-config.js";
import { FeaturePlugin } from "../src/plugin-server";
describe("FeaturePlugin", () => {
const { server, adminClient, shopClient } = createTestEnvironment(
mergeConfig(testConfig(), {
plugins: [
FeaturePlugin.init({
/* plugin options */
}),
],
}),
);
beforeAll(async () => {
await server.init({
initialData,
productsCsvPath: path.join(__dirname, "fixtures/e2e-products-minimal.csv"),
customerCount: 1,
});
await adminClient.asSuperAdmin();
}, TEST_SETUP_TIMEOUT_MS);
afterAll(async () => {
await server.destroy();
});
it("should create a feature via admin API", async () => {
const result = await adminClient.query(CREATE_FEATURE, {
input: { name: "Test Feature", enabled: true },
});
expect(result.createFeature).toEqual(
expect.objectContaining({ name: "Test Feature", enabled: true }),
);
});
it("should list features with pagination", async () => {
const result = await adminClient.query(LIST_FEATURES, {
options: { take: 10 },
});
expect(result.features.items).toHaveLength(1);
expect(result.features.totalItems).toBe(1);
});
});
// --- GraphQL documents (inline or in separate file) ---
const CREATE_FEATURE = gql`
mutation CreateFeature($input: CreateFeatureInput!) {
createFeature(input: $input) {
id
name
enabled
}
}
`;
const LIST_FEATURES = gql`
query ListFeatures($options: FeatureListOptions) {
features(options: $options) {
items {
id
name
enabled
}
totalItems
}
}
`;
Key details from the real codebase:
createTestEnvironment() is called at describe scope (not inside beforeAll)mergeConfig(testConfig(), { ... }) merges shared config with plugin-specific overridestestConfig() auto-assigns a unique port so parallel test suites don't collideadminClient.asSuperAdmin() logs in as superadmin after server initTEST_SETUP_TIMEOUT_MS is 120s normally, 30min with E2E_DEBUG=trueThe adminClient and shopClient are SimpleGraphQLClient instances with these methods:
| Method | Description |
|---|---|
query(doc, variables?) | Execute any GraphQL query or mutation |
asSuperAdmin() | Login as superadmin |
asUserWithCredentials(user, pass) | Login as specific user |
asAnonymousUser() | Logout to anonymous |
setChannelToken(token) | Set channel for multi-channel tests |
queryStatus(doc, variables?) | Get HTTP status code (for error testing) |
fileUploadMutation({ mutation, filePaths, mapVariables }) | Upload files via multipart |
fetch(url, options?) | Raw HTTP fetch (includes auth headers) |
When a mutation returns a union type (Success | ErrorResult), use createErrorResultGuard to narrow the type and auto-fail on unexpected results:
import { createErrorResultGuard, ErrorResultGuard } from "@deenruv/testing";
const orderGuard: ErrorResultGuard<OrderFragment> =
createErrorResultGuard((input) => !!input.lines);
// In tests:
orderGuard.assertSuccess(result); // narrows to success type, fails if error
orderGuard.assertErrorResult(result); // narrows to error type, fails if success
# All tests from repo root
pnpm test
# E2E for a specific plugin
cd plugins/feature-plugin && pnpm e2e
# Watch mode
cd plugins/feature-plugin && pnpm e2e:watch
# Debug mode (30min timeout)
E2E_DEBUG=true pnpm e2e
# Specific database backend (default: sqljs)
DB=postgres pnpm e2e
E2E_DEBUG=true for 30-minute timeoutsBindings not found: Run pnpm rebuild @swc/core__data__/ directory in plugin folder for cached DB filestestConfig() auto-assigns ports, but manual overrides can collidelogger: new DefaultLogger({ level: LogLevel.Info }) in mergeConfig*.e2e-spec.ts in e2e/ directorye2e script added to plugin package.json using shared vitest configcreateTestEnvironment called at describe scope with mergeConfig(testConfig(), ...)plugins: [FeaturePlugin.init({...})]server.init({ initialData }) called in beforeAll with TEST_SETUP_TIMEOUT_MSadminClient.asSuperAdmin() called after server.init()server.destroy() called in afterAllgql from graphql-tagpnpm e2e passes from the plugin directory