@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 +157 -6
- package/dist/core/RimoriTestEnvironment.d.ts +26 -1
- package/dist/core/RimoriTestEnvironment.js +39 -12
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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:
|
|
424
|
+
body: responseBody,
|
|
398
425
|
});
|
|
399
426
|
}
|
|
400
427
|
/**
|