@rimori/playwright-testing 0.2.0 → 0.2.2

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
@@ -1,10 +1,10 @@
1
- # @rimori/playwright
1
+ # @rimori/playwright-testing
2
2
 
3
3
  Playwright testing utilities for Rimori plugins. This package provides a complete testing environment that simulates how plugins run within the Rimori application, including MessageChannel communication, API mocking, and event handling.
4
4
 
5
5
  ## Overview
6
6
 
7
- The `@rimori/playwright` package enables end-to-end testing of Rimori plugins by:
7
+ The `@rimori/playwright-testing` package enables end-to-end testing of Rimori plugins by:
8
8
 
9
9
  - **Simulating iframe environment**: Makes plugins think they're running in an iframe (not standalone mode)
10
10
  - **MessageChannel simulation**: Mimics the parent-iframe communication used in production
@@ -14,16 +14,167 @@ The `@rimori/playwright` package enables end-to-end testing of Rimori plugins by
14
14
  ## Installation
15
15
 
16
16
  ```bash
17
- npm install --save-dev @rimori/playwright @playwright/test
17
+ npm install --save-dev @rimori/playwright-testing @playwright/test
18
18
  # or
19
- pnpm add -D @rimori/playwright @playwright/test
19
+ pnpm add -D @rimori/playwright-testing @playwright/test
20
+ # or
21
+ yarn add -D @rimori/playwright-testing @playwright/test
22
+ ```
23
+
24
+ ## Setup Steps
25
+
26
+ To initialize Playwright testing in your Rimori plugin, follow these steps:
27
+
28
+ ### 1. Install Dependencies
29
+
30
+ Add the required dependencies to your `package.json`:
31
+
32
+ ```json
33
+ {
34
+ "devDependencies": {
35
+ "@playwright/test": "^1.40.0",
36
+ "@rimori/playwright-testing": "^0.2.1"
37
+ }
38
+ }
39
+ ```
40
+
41
+ Then run:
42
+
43
+ ```bash
44
+ npm install
45
+ # or
46
+ yarn install
47
+ # or
48
+ pnpm install
49
+ ```
50
+
51
+ ### 2. Create Playwright Configuration
52
+
53
+ Create a `playwright.config.ts` file in your plugin root:
54
+
55
+ ```typescript
56
+ import { defineConfig, devices } from '@playwright/test';
57
+
58
+ export default defineConfig({
59
+ testDir: './test',
60
+ fullyParallel: true,
61
+ forbidOnly: !!process.env.CI,
62
+ retries: process.env.CI ? 2 : 0,
63
+ workers: process.env.CI ? 1 : undefined,
64
+ reporter: 'html',
65
+ use: {
66
+ trace: 'on-first-retry',
67
+ headless: false,
68
+ screenshot: 'only-on-failure',
69
+ },
70
+ timeout: 30000,
71
+ expect: {
72
+ timeout: 5000,
73
+ },
74
+ projects: [
75
+ {
76
+ name: 'chromium',
77
+ use: { ...devices['Desktop Chrome'] },
78
+ },
79
+ ],
80
+ });
81
+ ```
82
+
83
+ ### 3. Add Test Scripts
84
+
85
+ Add test scripts to your `package.json`:
86
+
87
+ ```json
88
+ {
89
+ "scripts": {
90
+ "test": "playwright test",
91
+ "test:headed": "playwright test --headed",
92
+ "test:debug": "playwright test --debug",
93
+ "test:ui": "playwright test --ui",
94
+ "test:headed:debug": "playwright test --headed --debug"
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### 4. Create Test Directory and Files
100
+
101
+ Create a `test` directory in your plugin root and add your test files:
102
+
103
+ ```typescript
104
+ // test/my-plugin.test.ts
105
+ import { test, expect } from '@playwright/test';
106
+ import { RimoriTestEnvironment } from '@rimori/playwright-testing';
107
+
108
+ const pluginId = 'pl1234567890'; // Your plugin ID from rimori.config.ts
109
+ const pluginUrl = 'http://localhost:3002'; // Your dev server URL
110
+
111
+ test.describe('My Plugin', () => {
112
+ let env: RimoriTestEnvironment;
113
+
114
+ test.beforeEach(async ({ page }) => {
115
+ env = new RimoriTestEnvironment({ page, pluginId });
116
+
117
+ // Set up your mocks here
118
+ // env.ai.mockGetObject(...);
119
+ // env.plugin.mockGetSettings(...);
120
+
121
+ await env.setup();
122
+ await page.goto(`${pluginUrl}/#/your-page`);
123
+ });
124
+
125
+ test('should work correctly', async ({ page }) => {
126
+ await expect(page.getByText('Hello')).toBeVisible();
127
+ });
128
+ });
129
+ ```
130
+
131
+ ### 5. Get Your Plugin ID
132
+
133
+ Find your plugin ID in `rimori/rimori.config.ts`:
134
+
135
+ ```typescript
136
+ const config: RimoriPluginConfig = {
137
+ id: 'pl1234567890', // <-- This is your plugin ID
138
+ // ...
139
+ };
140
+ ```
141
+
142
+ ### 6. Run Tests
143
+
144
+ 1. **Start your dev server** in one terminal:
145
+
146
+ ```bash
147
+ npm run dev
148
+ # or
149
+ yarn dev
150
+ ```
151
+
152
+ 2. **Run tests** in another terminal:
153
+ ```bash
154
+ npm test
155
+ # or
156
+ yarn test
157
+ ```
158
+
159
+ ### Complete Example Setup
160
+
161
+ Here's a complete example of what your plugin structure should look like:
162
+
163
+ ```
164
+ your-plugin/
165
+ ├── package.json # With @playwright/test and test scripts
166
+ ├── playwright.config.ts # Playwright configuration
167
+ ├── rimori/
168
+ │ └── rimori.config.ts # Contains your plugin ID
169
+ └── test/
170
+ └── my-plugin.test.ts # Your test files
20
171
  ```
21
172
 
22
173
  ## Quick Start
23
174
 
24
175
  ```typescript
25
176
  import { test, expect } from '@playwright/test';
26
- import { RimoriTestEnvironment } from '@rimori/playwright';
177
+ import { RimoriTestEnvironment } from '@rimori/playwright-testing';
27
178
 
28
179
  const pluginId = 'pl7720512027';
29
180
  const pluginUrl = 'http://localhost:3009';
@@ -338,7 +489,7 @@ env.ai.mockGetObject(
338
489
 
339
490
  ```typescript
340
491
  import { test, expect } from '@playwright/test';
341
- import { RimoriTestEnvironment } from '@rimori/playwright';
492
+ import { RimoriTestEnvironment } from '@rimori/playwright-testing';
342
493
 
343
494
  const pluginId = 'pl7720512027';
344
495
  const pluginUrl = 'http://localhost:3009';
@@ -26,6 +26,12 @@ interface MockOptions {
26
26
  * The HTTP method for the route. If not provided, defaults will be used based on the route type.
27
27
  */
28
28
  method?: HttpMethod;
29
+ /**
30
+ * If true, the mock is removed after first use. Default: false (persistent).
31
+ * This allows for sequential mock responses where each mock is consumed once.
32
+ * Useful for testing flows where the same route is called multiple times with different responses.
33
+ */
34
+ once?: boolean;
29
35
  }
30
36
  export declare class RimoriTestEnvironment {
31
37
  private readonly page;
@@ -50,6 +56,10 @@ export declare class RimoriTestEnvironment {
50
56
  * Creates a route key combining HTTP method and normalized URL.
51
57
  */
52
58
  private createRouteKey;
59
+ /**
60
+ * Removes a one-time mock from the mocks array after it's been used.
61
+ */
62
+ private removeOneTimeMock;
53
63
  private handleRoute;
54
64
  /**
55
65
  * Adds a supabase route to the supabase routes object.
@@ -96,7 +106,22 @@ export declare class RimoriTestEnvironment {
96
106
  mockGetPluginInfo: (pluginInfo: Plugin, options?: MockOptions) => void;
97
107
  };
98
108
  readonly db: {
99
- mockFrom: () => void;
109
+ /**
110
+ * Mocks a Supabase table endpoint (from(tableName)).
111
+ * The table name will be prefixed with the plugin ID in the actual URL.
112
+ *
113
+ * Supabase operations map to HTTP methods as follows:
114
+ * - .select() → GET
115
+ * - .insert() → POST
116
+ * - .update() → PATCH
117
+ * - .delete() → DELETE (can return data with .delete().select())
118
+ * - .upsert() → POST
119
+ *
120
+ * @param tableName - The table name (e.g., 'decks')
121
+ * @param value - The response value to return for the request
122
+ * @param options - Mock options including HTTP method (defaults to 'GET' if not specified)
123
+ */
124
+ mockFrom: (tableName: string, value: unknown, options?: MockOptions) => void;
100
125
  mockTable: () => void;
101
126
  };
102
127
  readonly event: {
@@ -43,6 +43,7 @@ class RimoriTestEnvironment {
43
43
  mockInsertSettings: (response, options) => {
44
44
  console.log('Mocking insert settings for mockInsertSettings', response, options);
45
45
  console.warn('mockInsertSettings is not tested');
46
+ // TODO this function should not exist and possibly be combined with the mockSetSettings function
46
47
  // POST request returns the inserted row or success response
47
48
  // Default to an object representing successful insert
48
49
  const defaultResponse = response ?? {
@@ -64,7 +65,26 @@ class RimoriTestEnvironment {
64
65
  },
65
66
  };
66
67
  this.db = {
67
- mockFrom: () => { },
68
+ /**
69
+ * Mocks a Supabase table endpoint (from(tableName)).
70
+ * The table name will be prefixed with the plugin ID in the actual URL.
71
+ *
72
+ * Supabase operations map to HTTP methods as follows:
73
+ * - .select() → GET
74
+ * - .insert() → POST
75
+ * - .update() → PATCH
76
+ * - .delete() → DELETE (can return data with .delete().select())
77
+ * - .upsert() → POST
78
+ *
79
+ * @param tableName - The table name (e.g., 'decks')
80
+ * @param value - The response value to return for the request
81
+ * @param options - Mock options including HTTP method (defaults to 'GET' if not specified)
82
+ */
83
+ mockFrom: (tableName, value, options) => {
84
+ console.log('Mocking db.from for table:', tableName, 'method:', options?.method ?? 'GET', value, options);
85
+ const fullTableName = `${this.pluginId}_${tableName}`;
86
+ this.addSupabaseRoute(fullTableName, value, options);
87
+ },
68
88
  mockTable: () => { },
69
89
  };
70
90
  this.event = {
@@ -86,15 +106,11 @@ class RimoriTestEnvironment {
86
106
  throw new Error('MessageChannelSimulator not initialized. Call setup() first.');
87
107
  }
88
108
  const topic = `${this.pluginId}.action.requestSidebar`;
89
- console.log('[RimoriTestEnvironment] Setting up listener for topic:', topic, 'with payload:', payload);
90
109
  const actionPayload = payload;
91
110
  const off = this.messageChannelSimulator.on(topic, async (event) => {
92
- console.log('[RimoriTestEnvironment] Received action.requestSidebar event:', event);
93
- console.log('[RimoriTestEnvironment] Responding to action.requestSidebar with payload:', actionPayload);
94
111
  await this.messageChannelSimulator.emit(topic, actionPayload, 'sidebar');
95
112
  off();
96
113
  });
97
- console.log('[RimoriTestEnvironment] Listener set up for topic:', topic);
98
114
  },
99
115
  /**
100
116
  * Triggers a main panel action event as the parent application would.
@@ -111,20 +127,16 @@ class RimoriTestEnvironment {
111
127
  // Listen for when the plugin emits 'action.requestMain' (which becomes '{pluginId}.action.requestMain')
112
128
  // and respond with the MainPanelAction payload, matching rimori-main's EventBus.respond behavior
113
129
  const topic = `${this.pluginId}.action.requestMain`;
114
- console.log('[RimoriTestEnvironment] Setting up listener for topic:', topic, 'with payload:', payload);
115
130
  // Store the payload in a closure so we can respond with it
116
131
  const actionPayload = payload;
117
132
  // Set up a one-time listener that responds when the plugin emits 'action.requestMain'
118
133
  // The handler receives the event object from the plugin
119
134
  const off = this.messageChannelSimulator.on(topic, async (event) => {
120
- console.log('[RimoriTestEnvironment] Received action.requestMain event:', event);
121
- console.log('[RimoriTestEnvironment] Responding to action.requestMain with payload:', actionPayload);
122
135
  // When plugin emits 'action.requestMain', respond with the MainPanelAction data
123
136
  // The sender is 'mainPanel' to match rimori-main's MainPluginHandler behavior
124
137
  await this.messageChannelSimulator.emit(topic, actionPayload, 'mainPanel');
125
138
  off(); // Remove listener after responding once (one-time response like EventBus.respond)
126
139
  });
127
- console.log('[RimoriTestEnvironment] Listener set up for topic:', topic);
128
140
  },
129
141
  };
130
142
  this.ai = {
@@ -145,7 +157,6 @@ class RimoriTestEnvironment {
145
157
  */
146
158
  mockGetSteamedText: (text, options) => {
147
159
  console.log('Mocking get steamed text for mockGetSteamedText', text, options);
148
- console.warn('mockGetSteamedText is not tested');
149
160
  this.addBackendRoute('/ai/llm', text, { ...options, isStreaming: true });
150
161
  },
151
162
  mockGetVoice: (values, options) => {
@@ -160,7 +171,6 @@ class RimoriTestEnvironment {
160
171
  },
161
172
  mockGetObject: (value, options) => {
162
173
  console.log('Mocking get object for mockGetObject', value, options);
163
- console.warn('mockGetObject is not tested');
164
174
  this.addBackendRoute('/ai/llm-object', value, { ...options, method: 'POST' });
165
175
  },
166
176
  };
@@ -230,6 +240,9 @@ class RimoriTestEnvironment {
230
240
  }
231
241
  async setup() {
232
242
  console.log('Setting up RimoriTestEnvironment');
243
+ this.page.on('console', (msg) => {
244
+ console.log(`[browser:${msg.type()}]`, msg.text());
245
+ });
233
246
  // Add default handlers for common routes that plugins typically access
234
247
  // These can be overridden by explicit mock calls
235
248
  if (!this.supabaseRoutes[this.createRouteKey('GET', `${this.rimoriInfo.url}/rest/v1/plugin_settings`)]) {
@@ -334,6 +347,17 @@ class RimoriTestEnvironment {
334
347
  const normalizedUrl = this.normalizeUrl(url);
335
348
  return `${method} ${normalizedUrl}`;
336
349
  }
350
+ /**
351
+ * Removes a one-time mock from the mocks array after it's been used.
352
+ */
353
+ removeOneTimeMock(mock, mocks) {
354
+ if (!mock.options?.once)
355
+ return;
356
+ const index = mocks.indexOf(mock);
357
+ if (index > -1) {
358
+ mocks.splice(index, 1);
359
+ }
360
+ }
337
361
  async handleRoute(route, routes) {
338
362
  console.warn('handleRoute is not tested');
339
363
  const request = route.request();
@@ -378,6 +402,8 @@ class RimoriTestEnvironment {
378
402
  // Handle the matched mock
379
403
  const options = matchingMock.options;
380
404
  await new Promise((resolve) => setTimeout(resolve, options?.delay ?? 0));
405
+ // Remove one-time mock after handling (before responding)
406
+ this.removeOneTimeMock(matchingMock, mocks);
381
407
  if (options?.error) {
382
408
  return await route.abort(options.error);
383
409
  }
@@ -392,9 +418,10 @@ class RimoriTestEnvironment {
392
418
  });
393
419
  }
394
420
  // Regular JSON response
421
+ const responseBody = JSON.stringify(matchingMock.value);
395
422
  route.fulfill({
396
423
  status: 200,
397
- body: JSON.stringify(matchingMock.value),
424
+ body: responseBody,
398
425
  });
399
426
  }
400
427
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/playwright-testing",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Playwright testing utilities for Rimori plugins and workers",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",