@rimori/playwright-testing 0.3.10 → 0.3.11-next.1

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.
@@ -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, studyPlan }?: SetupOptions): Promise<void>;
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,8 +2,8 @@
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
9
  const BACKEND_URL = 'http://localhost:2800';
@@ -21,7 +21,7 @@ class RimoriE2ETestEnvironment {
21
21
  throw new Error('RIMORI_TOKEN is not set as an environment variable.');
22
22
  }
23
23
  }
24
- async setup({ onboarding, exercises, studyPlan } = {}) {
24
+ async setup({ onboarding, exercises } = {}) {
25
25
  const onboardingData = {
26
26
  learning_reason: onboarding?.learning_reason ?? 'work',
27
27
  target_country: onboarding?.target_country ?? 'SE',
@@ -34,35 +34,34 @@ class RimoriE2ETestEnvironment {
34
34
  this.existingUserEmail = persist.email;
35
35
  console.log(`[E2E] Test user (temp): ${temp.email}`);
36
36
  console.log(`[E2E] Existing user (persist): ${persist.email}`);
37
- this.tempUserContext = await this.browser.newContext({ baseURL: RIMORI_URL });
38
37
  this.persistentUserContext = await this.browser.newContext({ baseURL: RIMORI_URL });
39
- await this.setupConsoleLogging(this.tempUserContext, 'temp');
40
38
  await this.setupConsoleLogging(this.persistentUserContext, 'persist');
41
39
  console.log(`[E2E] Preparing existing user context`);
40
+ const persistPage = await this.persistentUserContext.newPage();
42
41
  // Step 2: Set up existing user browser context with session via magic link
43
- await this.setSessionFromMagicLink(this.persistentUserContext, persist.magicLink);
42
+ await this.setSessionFromMagicLink(persistPage, persist.magicLink);
44
43
  // Step 3: Run onboarding for existing user
45
- await this.completeOnboarding(this.persistentUserContext, onboardingData);
44
+ await this.completeOnboarding(persistPage, onboardingData);
45
+ persistPage.close();
46
46
  console.log(`[E2E] Setting up test user context`);
47
+ this.tempUserContext = await this.browser.newContext({ baseURL: RIMORI_URL });
48
+ await this.setupConsoleLogging(this.tempUserContext, 'temp');
49
+ const tempPage = await this.tempUserContext.newPage();
47
50
  // Step 4: Set up test user browser context with session via magic link
48
- await this.setSessionFromMagicLink(this.tempUserContext, temp.magicLink);
51
+ await this.setSessionFromMagicLink(tempPage, temp.magicLink);
49
52
  // Delete test user when test user context is closed
50
53
  this.tempUserContext.on('close', async () => {
51
54
  await this.deleteTestUserViaApi(temp.email);
52
55
  console.log(`[E2E] Deleted test user: ${temp.email}`);
53
56
  });
54
57
  // Step 5: Run onboarding for test user with e2e plugin flag
55
- await this.completeOnboarding(this.tempUserContext, onboardingData, this.pluginId);
58
+ await this.completeOnboarding(tempPage, onboardingData, this.pluginId);
56
59
  // Step 6: Add exercises if specified
57
60
  if (exercises && exercises?.length > 0) {
58
61
  console.log(`[E2E] Setting up exercises`);
59
- await this.completeExerciseSetup(this.tempUserContext, exercises);
60
- }
61
- // Step 7: Complete study plan creation if specified
62
- if (studyPlan?.complete) {
63
- console.log(`[E2E] Setting up study plan`);
64
- await this.completeStudyPlanCreation(this.tempUserContext);
62
+ await this.completeExerciseSetup(tempPage, exercises);
65
63
  }
64
+ tempPage.close();
66
65
  console.log(`[E2E] Setup completed`);
67
66
  }
68
67
  async getTempUserPage() {
@@ -136,23 +135,24 @@ class RimoriE2ETestEnvironment {
136
135
  return;
137
136
  if (logMessage.includes('i18next: initialized {debug: true'))
138
137
  return;
138
+ if (logMessage.includes('i18next is maintained'))
139
+ return;
139
140
  console.log(`[browser:${logLevel}] [${user}]`, logMessage);
140
141
  });
141
142
  }
142
- async setSessionFromMagicLink(context, magicLink) {
143
- const page = await context.newPage();
143
+ async setSessionFromMagicLink(page, magicLink) {
144
144
  await page.goto(magicLink, { waitUntil: 'networkidle' });
145
- await page.waitForTimeout(5000);
146
- const url = page.url();
147
- if (!url.includes('/dashboard') && !url.includes('/onboarding')) {
145
+ try {
146
+ await page.waitForURL((url) => url.pathname.includes('/dashboard') || url.pathname.includes('/onboarding'), { timeout: 30000 });
147
+ }
148
+ catch {
149
+ const url = page.url();
148
150
  throw new Error(`Failed to set session from magic link: ${url}`);
149
151
  }
150
- await page.close();
151
152
  console.log(`[E2E] Authentication completed`);
152
153
  }
153
- async completeOnboarding(context, onboarding, e2ePluginId) {
154
+ async completeOnboarding(page, onboarding, e2ePluginId) {
154
155
  console.log(`[E2E] Starting onboarding`);
155
- const page = await context.newPage();
156
156
  await page.goto('/onboarding');
157
157
  await page.waitForTimeout(5000);
158
158
  const isOnboaded = page.url().includes('/dashboard');
@@ -164,22 +164,11 @@ class RimoriE2ETestEnvironment {
164
164
  else {
165
165
  console.log(`[E2E] User already onboarded`);
166
166
  }
167
- await page.close();
168
167
  }
169
- async completeExerciseSetup(context, exercises) {
170
- const page = await context.newPage();
168
+ async completeExerciseSetup(page, exercises) {
171
169
  for (const exercise of exercises) {
172
- const encoded = encodeURIComponent(JSON.stringify(exercise));
173
- await page.goto(`${RIMORI_URL}/dashboard?flag-e2e-exercise=${encoded}`);
174
- // Wait for the exercise to be created and the flag to be cleared from URL
175
- await page.waitForURL((url) => !url.searchParams.has('flag-e2e-exercise'), { timeout: 15000 });
170
+ await (0, create_exercise_1.createExerciseViaDialog)(page, exercise);
176
171
  }
177
- await page.close();
178
- }
179
- async completeStudyPlanCreation(context) {
180
- const page = await context.newPage();
181
- await (0, study_plan_setup_1.completeStudyPlanGettingStarted)(page);
182
- await page.close();
183
172
  }
184
173
  }
185
174
  exports.RimoriE2ETestEnvironment = RimoriE2ETestEnvironment;
@@ -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
+ }
@@ -24,6 +24,7 @@ async function completeOnboarding(page, onboarding, e2ePluginId) {
24
24
  page.setDefaultNavigationTimeout(60000);
25
25
  // Ensure we're on onboarding page
26
26
  await (0, test_1.expect)(page).toHaveURL(/\/onboarding/);
27
+ await page.waitForTimeout(2000);
27
28
  // Step 1: Learning Reason (radio select, auto-advances after selection)
28
29
  const learningReasonOption = page.getByText(learningReasonMap[onboarding.learning_reason]);
29
30
  await (0, test_1.expect)(learningReasonOption).toBeVisible({ timeout: 10000 });
@@ -34,6 +35,7 @@ async function completeOnboarding(page, onboarding, e2ePluginId) {
34
35
  await (0, test_1.expect)(interestsTextarea).toBeVisible({ timeout: 10000 });
35
36
  await interestsTextarea.click();
36
37
  await interestsTextarea.fill(onboarding.interests);
38
+ await page.waitForTimeout(1000);
37
39
  const interestsContinue = page.getByRole('button', { name: /continue/i });
38
40
  await (0, test_1.expect)(interestsContinue).toBeEnabled({ timeout: 10000 });
39
41
  await interestsContinue.click();
@@ -56,11 +58,9 @@ async function completeOnboarding(page, onboarding, e2ePluginId) {
56
58
  await (0, test_1.expect)(page.getByRole('heading', { name: 'Almost there!' })).toBeVisible({ timeout: 10000 });
57
59
  await page.waitForURL('**/dashboard', { timeout: 120000 });
58
60
  await (0, test_1.expect)(page.getByRole('heading', { name: "Today's Mission" })).toBeVisible({ timeout: 30000 });
61
+ //navbar should get shown
59
62
  await (0, test_1.expect)(page.getByRole('link', { name: 'Grammar', exact: true })).toBeVisible({ timeout: 60000 });
60
- await (0, test_1.expect)(page.getByRole('heading', { name: 'Getting Started: Create your first study plan' })).toBeVisible({
61
- timeout: 60000,
62
- });
63
- await (0, test_1.expect)(page.getByText('Train your first flashcard deck', { exact: true })).toBeVisible({ timeout: 200000 });
63
+ //support sidepanel should be open
64
64
  await (0, test_1.expect)(page.locator('iframe').contentFrame().getByText('Getting Started', { exact: true })).toBeVisible({
65
65
  timeout: 250000,
66
66
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/playwright-testing",
3
- "version": "0.3.10",
3
+ "version": "0.3.11-next.1",
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.17"
29
+ "@rimori/client": "2.5.18-next.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@playwright/test": "^1.40.0",
33
- "@rimori/client": "^2.5.17",
33
+ "@rimori/client": "2.5.18-next.0",
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
- }