@rimori/playwright-testing 0.3.11 → 0.3.12-next.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/dist/core/RimoriE2ETestEnvironment.d.ts +1 -5
- package/dist/core/RimoriE2ETestEnvironment.js +10 -18
- package/dist/core/RimoriTestEnvironment.js +2 -0
- package/dist/helpers/e2e/create-exercise.d.ts +19 -0
- package/dist/helpers/e2e/create-exercise.js +70 -0
- package/dist/helpers/e2e/onboarding.js +2 -4
- package/package.json +3 -3
- package/dist/helpers/e2e/study-plan-setup.d.ts +0 -11
- package/dist/helpers/e2e/study-plan-setup.js +0 -54
|
@@ -14,9 +14,6 @@ interface Exercise {
|
|
|
14
14
|
interface SetupOptions {
|
|
15
15
|
onboarding?: Onboarding;
|
|
16
16
|
exercises?: Array<Exercise>;
|
|
17
|
-
studyPlan?: {
|
|
18
|
-
complete: boolean;
|
|
19
|
-
};
|
|
20
17
|
}
|
|
21
18
|
export declare class RimoriE2ETestEnvironment {
|
|
22
19
|
private browser;
|
|
@@ -27,7 +24,7 @@ export declare class RimoriE2ETestEnvironment {
|
|
|
27
24
|
private existingUserEmail;
|
|
28
25
|
private authToken;
|
|
29
26
|
constructor(options: RimoriE2ETestEnvironmentOptions);
|
|
30
|
-
setup({ onboarding, exercises
|
|
27
|
+
setup({ onboarding, exercises }?: SetupOptions): Promise<void>;
|
|
31
28
|
getTempUserPage(): Promise<Page>;
|
|
32
29
|
getPersistUserPage(): Promise<Page>;
|
|
33
30
|
getTempUserEmail(): string;
|
|
@@ -38,6 +35,5 @@ export declare class RimoriE2ETestEnvironment {
|
|
|
38
35
|
private setSessionFromMagicLink;
|
|
39
36
|
private completeOnboarding;
|
|
40
37
|
private completeExerciseSetup;
|
|
41
|
-
private completeStudyPlanCreation;
|
|
42
38
|
}
|
|
43
39
|
export {};
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RimoriE2ETestEnvironment = void 0;
|
|
4
4
|
const dotenv_1 = require("dotenv");
|
|
5
|
-
const study_plan_setup_1 = require("../helpers/e2e/study-plan-setup");
|
|
6
5
|
const onboarding_1 = require("../helpers/e2e/onboarding");
|
|
6
|
+
const create_exercise_1 = require("../helpers/e2e/create-exercise");
|
|
7
7
|
(0, dotenv_1.config)();
|
|
8
8
|
const RIMORI_URL = 'https://dev-app.rimori.se';
|
|
9
|
-
const BACKEND_URL = 'http://localhost:2800';
|
|
9
|
+
// const BACKEND_URL = 'http://localhost:2800';
|
|
10
|
+
const BACKEND_URL = 'https://dev-api.rimori.se';
|
|
10
11
|
class RimoriE2ETestEnvironment {
|
|
11
12
|
constructor(options) {
|
|
12
13
|
this.persistentUserContext = null;
|
|
@@ -21,7 +22,7 @@ class RimoriE2ETestEnvironment {
|
|
|
21
22
|
throw new Error('RIMORI_TOKEN is not set as an environment variable.');
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
|
-
async setup({ onboarding, exercises
|
|
25
|
+
async setup({ onboarding, exercises } = {}) {
|
|
25
26
|
const onboardingData = {
|
|
26
27
|
learning_reason: onboarding?.learning_reason ?? 'work',
|
|
27
28
|
target_country: onboarding?.target_country ?? 'SE',
|
|
@@ -61,11 +62,6 @@ class RimoriE2ETestEnvironment {
|
|
|
61
62
|
console.log(`[E2E] Setting up exercises`);
|
|
62
63
|
await this.completeExerciseSetup(tempPage, exercises);
|
|
63
64
|
}
|
|
64
|
-
// Step 7: Complete study plan creation if specified
|
|
65
|
-
if (studyPlan?.complete) {
|
|
66
|
-
console.log(`[E2E] Setting up study plan`);
|
|
67
|
-
await this.completeStudyPlanCreation(tempPage);
|
|
68
|
-
}
|
|
69
65
|
tempPage.close();
|
|
70
66
|
console.log(`[E2E] Setup completed`);
|
|
71
67
|
}
|
|
@@ -147,9 +143,11 @@ class RimoriE2ETestEnvironment {
|
|
|
147
143
|
}
|
|
148
144
|
async setSessionFromMagicLink(page, magicLink) {
|
|
149
145
|
await page.goto(magicLink, { waitUntil: 'networkidle' });
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
try {
|
|
147
|
+
await page.waitForURL((url) => url.pathname.includes('/dashboard') || url.pathname.includes('/onboarding'), { timeout: 30000 });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
const url = page.url();
|
|
153
151
|
throw new Error(`Failed to set session from magic link: ${url}`);
|
|
154
152
|
}
|
|
155
153
|
console.log(`[E2E] Authentication completed`);
|
|
@@ -170,14 +168,8 @@ class RimoriE2ETestEnvironment {
|
|
|
170
168
|
}
|
|
171
169
|
async completeExerciseSetup(page, exercises) {
|
|
172
170
|
for (const exercise of exercises) {
|
|
173
|
-
|
|
174
|
-
await page.goto(`${RIMORI_URL}/dashboard?flag-e2e-exercise=${encoded}`);
|
|
175
|
-
// Wait for the exercise to be created and the flag to be cleared from URL
|
|
176
|
-
await page.waitForURL((url) => !url.searchParams.has('flag-e2e-exercise'), { timeout: 15000 });
|
|
171
|
+
await (0, create_exercise_1.createExerciseViaDialog)(page, exercise);
|
|
177
172
|
}
|
|
178
173
|
}
|
|
179
|
-
async completeStudyPlanCreation(page) {
|
|
180
|
-
await (0, study_plan_setup_1.completeStudyPlanGettingStarted)(page);
|
|
181
|
-
}
|
|
182
174
|
}
|
|
183
175
|
exports.RimoriE2ETestEnvironment = RimoriE2ETestEnvironment;
|
|
@@ -224,6 +224,8 @@ class RimoriTestEnvironment {
|
|
|
224
224
|
this.addBackendRoute('/ai/llm', { result: text }, { ...options, isStreaming: true });
|
|
225
225
|
},
|
|
226
226
|
mockGetVoice: (values, options) => {
|
|
227
|
+
// getVoice() calls ensureSessionToken() first, so mock the session endpoint too
|
|
228
|
+
this.addBackendRoute('/ai/session', { session_token_id: 'mock-session-token' }, { method: 'POST' });
|
|
227
229
|
this.addBackendRoute('/voice/tts', values, options);
|
|
228
230
|
},
|
|
229
231
|
mockGetTextFromVoice: (text, options) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Page } from '@playwright/test';
|
|
2
|
+
interface Exercise {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
pluginId: string;
|
|
6
|
+
actionKey: string;
|
|
7
|
+
parameters?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates an exercise via the CreateExerciseDialog in rimori-main.
|
|
11
|
+
*
|
|
12
|
+
* Navigates to the dashboard, opens the "Create exercise" dialog, and walks
|
|
13
|
+
* through all 4 steps to create the exercise.
|
|
14
|
+
*
|
|
15
|
+
* @param page - Playwright page instance, should be authenticated on the dashboard
|
|
16
|
+
* @param exercise - Exercise definition with plugin ID, action key, and parameters
|
|
17
|
+
*/
|
|
18
|
+
export declare function createExerciseViaDialog(page: Page, exercise: Exercise): Promise<void>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createExerciseViaDialog = createExerciseViaDialog;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
/**
|
|
6
|
+
* Creates an exercise via the CreateExerciseDialog in rimori-main.
|
|
7
|
+
*
|
|
8
|
+
* Navigates to the dashboard, opens the "Create exercise" dialog, and walks
|
|
9
|
+
* through all 4 steps to create the exercise.
|
|
10
|
+
*
|
|
11
|
+
* @param page - Playwright page instance, should be authenticated on the dashboard
|
|
12
|
+
* @param exercise - Exercise definition with plugin ID, action key, and parameters
|
|
13
|
+
*/
|
|
14
|
+
async function createExerciseViaDialog(page, exercise) {
|
|
15
|
+
await page.goto('/dashboard');
|
|
16
|
+
await page.waitForTimeout(2000);
|
|
17
|
+
// Open the Create Exercise dialog via the StudyBuddy section button
|
|
18
|
+
const createButton = page.getByRole('button', { name: 'Create exercise', exact: true });
|
|
19
|
+
await (0, test_1.expect)(createButton).toBeVisible({ timeout: 10000 });
|
|
20
|
+
await createButton.click();
|
|
21
|
+
// Wait for dialog to open
|
|
22
|
+
const dialog = page.locator('[role="dialog"]');
|
|
23
|
+
await (0, test_1.expect)(dialog).toBeVisible({ timeout: 5000 });
|
|
24
|
+
// Step 1: Select the action matching pluginId + actionKey
|
|
25
|
+
const actionButton = dialog.locator(`[data-plugin-id="${exercise.pluginId}"][data-action-key="${exercise.actionKey}"]`);
|
|
26
|
+
await (0, test_1.expect)(actionButton).toBeVisible({ timeout: 10000 });
|
|
27
|
+
await actionButton.click();
|
|
28
|
+
// Click "Next" to proceed to step 2
|
|
29
|
+
await dialog.getByRole('button', { name: 'Next' }).click();
|
|
30
|
+
// Step 2: Fill in exercise name and optional description
|
|
31
|
+
const nameInput = dialog.locator('input#exercise-name');
|
|
32
|
+
await (0, test_1.expect)(nameInput).toBeVisible({ timeout: 5000 });
|
|
33
|
+
await nameInput.fill(exercise.title);
|
|
34
|
+
if (exercise.description) {
|
|
35
|
+
const descInput = dialog.locator('textarea#exercise-description');
|
|
36
|
+
await descInput.fill(exercise.description);
|
|
37
|
+
}
|
|
38
|
+
// Keep default dates (today → today+7 days) — they are valid by default
|
|
39
|
+
await dialog.getByRole('button', { name: 'Next' }).click();
|
|
40
|
+
// Step 3: Fill in action parameters
|
|
41
|
+
if (exercise.parameters) {
|
|
42
|
+
for (const [key, value] of Object.entries(exercise.parameters)) {
|
|
43
|
+
// Try combobox (Radix Select) first — rendered with role="combobox"
|
|
44
|
+
const combobox = dialog.locator(`[id="param-${key}"][role="combobox"]`);
|
|
45
|
+
if (await combobox.count() > 0) {
|
|
46
|
+
await combobox.click();
|
|
47
|
+
await page.getByRole('option', { name: String(value), exact: true }).click();
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Number input
|
|
51
|
+
const numberInput = dialog.locator(`input#param-${key}[type="number"]`);
|
|
52
|
+
if (await numberInput.count() > 0) {
|
|
53
|
+
await numberInput.fill(String(value));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Text input (default)
|
|
57
|
+
const textInput = dialog.locator(`input#param-${key}`);
|
|
58
|
+
if (await textInput.count() > 0) {
|
|
59
|
+
await textInput.fill(String(value));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await dialog.getByRole('button', { name: 'Next' }).click();
|
|
64
|
+
// Step 4: Create (without sharing)
|
|
65
|
+
const createExerciseButton = dialog.getByRole('button', { name: 'Create exercise' });
|
|
66
|
+
await (0, test_1.expect)(createExerciseButton).toBeEnabled({ timeout: 5000 });
|
|
67
|
+
await createExerciseButton.click();
|
|
68
|
+
// Wait for dialog to close (exercise created successfully)
|
|
69
|
+
await (0, test_1.expect)(dialog).toBeHidden({ timeout: 15000 });
|
|
70
|
+
}
|
|
@@ -58,11 +58,9 @@ async function completeOnboarding(page, onboarding, e2ePluginId) {
|
|
|
58
58
|
await (0, test_1.expect)(page.getByRole('heading', { name: 'Almost there!' })).toBeVisible({ timeout: 10000 });
|
|
59
59
|
await page.waitForURL('**/dashboard', { timeout: 120000 });
|
|
60
60
|
await (0, test_1.expect)(page.getByRole('heading', { name: "Today's Mission" })).toBeVisible({ timeout: 30000 });
|
|
61
|
+
//navbar should get shown
|
|
61
62
|
await (0, test_1.expect)(page.getByRole('link', { name: 'Grammar', exact: true })).toBeVisible({ timeout: 60000 });
|
|
62
|
-
|
|
63
|
-
timeout: 60000,
|
|
64
|
-
});
|
|
65
|
-
await (0, test_1.expect)(page.getByText('Train your first flashcard deck', { exact: true })).toBeVisible({ timeout: 200000 });
|
|
63
|
+
//support sidepanel should be open
|
|
66
64
|
await (0, test_1.expect)(page.locator('iframe').contentFrame().getByText('Getting Started', { exact: true })).toBeVisible({
|
|
67
65
|
timeout: 250000,
|
|
68
66
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimori/playwright-testing",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12-next.0",
|
|
4
4
|
"description": "Playwright testing utilities for Rimori plugins and workers",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@playwright/test": "^1.40.0",
|
|
29
|
-
"@rimori/client": "^2.5.
|
|
29
|
+
"@rimori/client": "^2.5.19"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@playwright/test": "^1.40.0",
|
|
33
|
-
"@rimori/client": "^2.5.
|
|
33
|
+
"@rimori/client": "^2.5.19",
|
|
34
34
|
"@types/node": "^20.12.7",
|
|
35
35
|
"rimraf": "^5.0.7",
|
|
36
36
|
"typescript": "^5.7.2"
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Page } from '@playwright/test';
|
|
2
|
-
/**
|
|
3
|
-
* Navigates through the study plan getting-started flow on the dashboard.
|
|
4
|
-
* This clicks through the real UI: milestone planning (Submit Topics) and
|
|
5
|
-
* exercise creation (Save Exercises).
|
|
6
|
-
*
|
|
7
|
-
* Expects the page to already be on the dashboard with a "Getting Started" exercise visible.
|
|
8
|
-
*
|
|
9
|
-
* @param page - Playwright page instance, should be on the dashboard
|
|
10
|
-
*/
|
|
11
|
-
export declare function completeStudyPlanGettingStarted(page: Page): Promise<void>;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.completeStudyPlanGettingStarted = completeStudyPlanGettingStarted;
|
|
4
|
-
const test_1 = require("@playwright/test");
|
|
5
|
-
/**
|
|
6
|
-
* Navigates through the study plan getting-started flow on the dashboard.
|
|
7
|
-
* This clicks through the real UI: milestone planning (Submit Topics) and
|
|
8
|
-
* exercise creation (Save Exercises).
|
|
9
|
-
*
|
|
10
|
-
* Expects the page to already be on the dashboard with a "Getting Started" exercise visible.
|
|
11
|
-
*
|
|
12
|
-
* @param page - Playwright page instance, should be on the dashboard
|
|
13
|
-
*/
|
|
14
|
-
async function completeStudyPlanGettingStarted(page) {
|
|
15
|
-
page.goto('/dashboard');
|
|
16
|
-
await page.waitForTimeout(2000);
|
|
17
|
-
// Step 1: Find and click the Getting Started exercise card
|
|
18
|
-
const card = page.getByText('Getting Started: Create your first study plan', { exact: false });
|
|
19
|
-
await card.waitFor({ timeout: 10000, state: 'visible' }).catch(() => {
|
|
20
|
-
/* not visible within 10s, continue to early return below */
|
|
21
|
-
});
|
|
22
|
-
if (!(await card.isVisible())) {
|
|
23
|
-
page.close();
|
|
24
|
-
console.warn(`[E2E] Getting Started card not found, skipping study plan setup`);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const gettingStartedCard = page.getByText('Start Exercise', { exact: false }).first();
|
|
28
|
-
await (0, test_1.expect)(gettingStartedCard).toBeVisible({ timeout: 30000 });
|
|
29
|
-
await gettingStartedCard.click();
|
|
30
|
-
// Wait for the study plan plugin iframe to load
|
|
31
|
-
const iframe = page.locator('iframe').first();
|
|
32
|
-
await (0, test_1.expect)(iframe).toBeVisible({ timeout: 30000 });
|
|
33
|
-
const frame = iframe.contentFrame();
|
|
34
|
-
// Step 2: Milestone Planning Stage
|
|
35
|
-
// Wait for the 3 milestone cards to appear (AI generates them)
|
|
36
|
-
await (0, test_1.expect)(frame.getByText('Week 1', { exact: false })).toBeVisible({ timeout: 180000 });
|
|
37
|
-
await (0, test_1.expect)(frame.getByText('Week 2', { exact: false })).toBeVisible({ timeout: 10000 });
|
|
38
|
-
await (0, test_1.expect)(frame.getByText('Week 3', { exact: false })).toBeVisible({ timeout: 10000 });
|
|
39
|
-
// Wait for "Submit Topics" button to be enabled and click it
|
|
40
|
-
const submitTopicsButton = frame.getByRole('button', { name: /submit topics/i });
|
|
41
|
-
await (0, test_1.expect)(submitTopicsButton).toBeEnabled({ timeout: 180000 });
|
|
42
|
-
await submitTopicsButton.click();
|
|
43
|
-
// Step 3: Exercise Creation Stage
|
|
44
|
-
// Wait for "Save Exercises" button to appear (AI generates all exercises)
|
|
45
|
-
const saveExercisesButton = frame.getByRole('button', { name: /save exercises/i });
|
|
46
|
-
await (0, test_1.expect)(saveExercisesButton).toBeVisible({ timeout: 300000 });
|
|
47
|
-
await (0, test_1.expect)(saveExercisesButton).toBeEnabled({ timeout: 30000 });
|
|
48
|
-
await saveExercisesButton.click();
|
|
49
|
-
// Wait for save to complete (button should disappear or page navigates)
|
|
50
|
-
await (0, test_1.expect)(saveExercisesButton).toBeHidden({ timeout: 30000 });
|
|
51
|
-
// Step 4: Verify completion - should be back on dashboard
|
|
52
|
-
// The "Getting Started" card should be gone and exercises should appear
|
|
53
|
-
await (0, test_1.expect)(page.getByText("Today's Mission", { exact: false })).toBeVisible({ timeout: 30000 });
|
|
54
|
-
}
|