-
-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathcreate-agent-avatar.spec.js
More file actions
144 lines (124 loc) · 5.19 KB
/
Copy pathcreate-agent-avatar.spec.js
File metadata and controls
144 lines (124 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* Create-agent wizard — avatar step smoke.
*
* Verifies the "every agent gets a 3D body" rules added to step 2:
* • the four model tabs render (Starter / My avatars / Upload / Add later)
* • the step blocks advancing until a real choice is made
* • the "My avatars" tab loads the caller's library and lets you connect one
* • the "Add later" path is gated behind an explicit acknowledgment, after
* which the agent will launch with the default body
*
* Auth + the avatars list are fulfilled from the Playwright route layer so the
* client-side step logic can be exercised without a live session/DB. These are
* test fixtures for the harness — the product code still calls the real
* /api/auth/me and /api/avatars endpoints.
*/
import { test, expect } from '@playwright/test';
const FIXTURE_USER = {
id: 'usr_e2e_creator',
handle: 'e2e-creator',
display_name: 'E2E Creator',
plan: 'free',
};
// Synthetic avatars matching the real /api/avatars payload shape (model_url /
// base_model_url / thumbnail_url — there is no `url` field). The second avatar
// mimics a private one: no model_url, only a thumbnail. Both must still render
// and be selectable, since the agent links by id.
const FIXTURE_AVATARS = [
{
id: 'ava-e2e-one',
name: 'Test Avatar One',
model_url: '/avatars/default.glb',
base_model_url: '/avatars/default.glb',
thumbnail_url: '/favicon-32x32.png',
},
{
id: 'ava-e2e-two',
name: 'Test Avatar Two',
model_url: null,
base_model_url: null,
thumbnail_url: '/favicon-32x32.png',
},
];
async function installFixtures(page) {
await page.route('**/api/auth/me**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ user: FIXTURE_USER }),
}),
);
await page.route('**/api/avatars?**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ avatars: FIXTURE_AVATARS }),
}),
);
}
/** Advance from the Basics step into the 3D-model step with a valid name. */
async function gotoModelStep(page) {
await page.goto('/create-agent');
const name = page.locator('#f-name');
await name.waitFor({ state: 'visible', timeout: 30_000 });
await name.fill('E2E Test Agent');
await page.locator('#btn-next').click();
await expect(page.locator('.panel[data-step="1"].is-active')).toBeVisible();
}
test.describe('create-agent / avatar step', () => {
test.beforeEach(async ({ page }) => {
page.on('pageerror', (err) => {
// Ignore Vite's dev-only HMR socket noise — not a product error.
if (/WebSocket closed without opened/i.test(err.message)) return;
throw new Error(`Uncaught page error: ${err.message}`);
});
await installFixtures(page);
});
test('renders all four model tabs', async ({ page }) => {
test.setTimeout(90_000);
await gotoModelStep(page);
const tabs = page.locator('.model-tab');
await expect(tabs).toHaveCount(4);
await expect(page.locator('.model-tab[data-pane="starter"]')).toHaveText('Starter library');
await expect(page.locator('.model-tab[data-pane="library"]')).toHaveText('My avatars');
await expect(page.locator('.model-tab[data-pane="upload"]')).toHaveText('Upload your own');
await expect(page.locator('.model-tab[data-pane="skip"]')).toHaveText('Add later');
});
test('blocks advancing with no avatar chosen', async ({ page }) => {
test.setTimeout(90_000);
await gotoModelStep(page);
await page.locator('#btn-next').click();
// Still on the model step, with an error prompting a choice.
await expect(page.locator('.panel[data-step="1"].is-active')).toBeVisible();
await expect(page.locator('#foot-msg.err')).toContainText('Pick a starter avatar');
});
test('connects an avatar from "My avatars" and advances', async ({ page }) => {
test.setTimeout(90_000);
await gotoModelStep(page);
await page.locator('.model-tab[data-pane="library"]').click();
// Tiles render from the (fixtured) library feed.
const tiles = page.locator('#library-grid .starter');
await expect(tiles).toHaveCount(FIXTURE_AVATARS.length);
await expect(tiles.first()).toContainText('Test Avatar One');
await tiles.first().click();
await expect(tiles.first()).toHaveClass(/is-selected/);
// A connected avatar satisfies the step — Next advances to Skills.
await page.locator('#btn-next').click();
await expect(page.locator('.panel[data-step="2"].is-active')).toBeVisible();
});
test('"Add later" requires acknowledgment, then uses the default body', async ({ page }) => {
test.setTimeout(90_000);
await gotoModelStep(page);
await page.locator('.model-tab[data-pane="skip"]').click();
// Without the acknowledgment, the step is blocked.
await page.locator('#btn-next').click();
await expect(page.locator('.panel[data-step="1"].is-active')).toBeVisible();
await expect(page.locator('#foot-msg.err')).toContainText('default 3D body');
// Acknowledge via the visible toggle (the native input is CSS-covered by
// the track), then the step clears and advances.
await page.locator('.model-pane[data-pane="skip"] label.toggle').click();
await expect(page.locator('#f-skip-ack')).toBeChecked();
await page.locator('#btn-next').click();
await expect(page.locator('.panel[data-step="2"].is-active')).toBeVisible();
});
});