een-api-toolkit 0.3.43 → 0.3.47
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/.claude/agents/docs-accuracy-reviewer.md +35 -5
- package/.claude/agents/een-automations-agent.md +264 -0
- package/.claude/agents/een-devices-agent.md +5 -7
- package/.claude/agents/een-events-agent.md +30 -16
- package/.claude/agents/een-media-agent.md +12 -15
- package/.claude/agents/een-users-agent.md +2 -2
- package/CHANGELOG.md +6 -8
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +815 -0
- package/dist/index.js +986 -719
- package/dist/index.js.map +1 -1
- package/docs/AI-CONTEXT.md +17 -1
- package/docs/ai-reference/AI-AUTH.md +1 -1
- package/docs/ai-reference/AI-AUTOMATIONS.md +833 -0
- package/docs/ai-reference/AI-DEVICES.md +1 -1
- package/docs/ai-reference/AI-EVENTS.md +1 -1
- package/docs/ai-reference/AI-GROUPING.md +128 -66
- package/docs/ai-reference/AI-MEDIA.md +1 -1
- package/docs/ai-reference/AI-SETUP.md +1 -1
- package/docs/ai-reference/AI-USERS.md +1 -1
- package/examples/vue-automations/.env.example +11 -0
- package/examples/vue-automations/README.md +205 -0
- package/examples/vue-automations/e2e/app.spec.ts +83 -0
- package/examples/vue-automations/e2e/auth.spec.ts +468 -0
- package/examples/vue-automations/index.html +13 -0
- package/examples/vue-automations/package-lock.json +1722 -0
- package/examples/vue-automations/package.json +29 -0
- package/examples/vue-automations/playwright.config.ts +46 -0
- package/examples/vue-automations/src/App.vue +122 -0
- package/examples/vue-automations/src/main.ts +23 -0
- package/examples/vue-automations/src/router/index.ts +61 -0
- package/examples/vue-automations/src/views/Automations.vue +692 -0
- package/examples/vue-automations/src/views/Callback.vue +76 -0
- package/examples/vue-automations/src/views/Home.vue +172 -0
- package/examples/vue-automations/src/views/Login.vue +33 -0
- package/examples/vue-automations/src/views/Logout.vue +66 -0
- package/examples/vue-automations/src/vite-env.d.ts +1 -0
- package/examples/vue-automations/tsconfig.json +21 -0
- package/examples/vue-automations/tsconfig.node.json +10 -0
- package/examples/vue-automations/vite.config.ts +12 -0
- package/examples/vue-event-subscriptions/e2e/auth.spec.ts +8 -12
- package/package.json +1 -1
- package/scripts/setup-agents.ts +38 -19
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { test, expect, Page } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* E2E tests for the Vue Automations Example - OAuth Login Flow
|
|
5
|
+
*
|
|
6
|
+
* Tests the OAuth login flow through the UI:
|
|
7
|
+
* 1. Click login button in the example app
|
|
8
|
+
* 2. Enter credentials on EEN OAuth page
|
|
9
|
+
* 3. Complete the OAuth callback
|
|
10
|
+
* 4. Verify authenticated state and automations display
|
|
11
|
+
*
|
|
12
|
+
* Required environment variables:
|
|
13
|
+
* - VITE_PROXY_URL: OAuth proxy URL (e.g., http://127.0.0.1:8787)
|
|
14
|
+
* - VITE_EEN_CLIENT_ID: EEN OAuth client ID
|
|
15
|
+
* - TEST_USER: Test user email
|
|
16
|
+
* - TEST_PASSWORD: Test user password
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const TIMEOUTS = {
|
|
20
|
+
OAUTH_REDIRECT: 30000,
|
|
21
|
+
ELEMENT_VISIBLE: 15000,
|
|
22
|
+
PASSWORD_VISIBLE: 10000,
|
|
23
|
+
AUTH_COMPLETE: 30000,
|
|
24
|
+
UI_UPDATE: 10000,
|
|
25
|
+
PROXY_CHECK: 5000,
|
|
26
|
+
DATA_LOAD: 20000
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
const TEST_USER = process.env.TEST_USER
|
|
30
|
+
const TEST_PASSWORD = process.env.TEST_PASSWORD
|
|
31
|
+
const PROXY_URL = process.env.VITE_PROXY_URL
|
|
32
|
+
|
|
33
|
+
async function isProxyAccessible(): Promise<boolean> {
|
|
34
|
+
if (!PROXY_URL) return false
|
|
35
|
+
const controller = new AbortController()
|
|
36
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUTS.PROXY_CHECK)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(PROXY_URL, {
|
|
40
|
+
method: 'HEAD',
|
|
41
|
+
signal: controller.signal
|
|
42
|
+
})
|
|
43
|
+
return response.ok || response.status === 404
|
|
44
|
+
} catch {
|
|
45
|
+
return false
|
|
46
|
+
} finally {
|
|
47
|
+
clearTimeout(timeoutId)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function performLogin(page: Page, username: string, password: string): Promise<void> {
|
|
52
|
+
await page.goto('/')
|
|
53
|
+
|
|
54
|
+
// Click login button on home page to go to login page
|
|
55
|
+
await page.click('[data-testid="login-button"]')
|
|
56
|
+
await page.waitForURL('/login')
|
|
57
|
+
|
|
58
|
+
// Click the OAuth login button on the login page
|
|
59
|
+
await Promise.all([
|
|
60
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
61
|
+
page.click('button:has-text("Login with Eagle Eye Networks")')
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
65
|
+
await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
66
|
+
await emailInput.fill(username)
|
|
67
|
+
|
|
68
|
+
await page.getByRole('button', { name: 'Next' }).click()
|
|
69
|
+
|
|
70
|
+
const passwordInput = page.locator('#authentication--input__password')
|
|
71
|
+
await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
|
|
72
|
+
await passwordInput.fill(password)
|
|
73
|
+
|
|
74
|
+
await page.locator('#next, button:has-text("Sign in")').first().click()
|
|
75
|
+
|
|
76
|
+
// After login, the automations app redirects to /automations
|
|
77
|
+
await page.waitForURL('**/automations', { timeout: TIMEOUTS.AUTH_COMPLETE })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function clearAuthState(page: Page): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
const url = page.url()
|
|
83
|
+
if (url && url.startsWith('http')) {
|
|
84
|
+
await page.evaluate(() => {
|
|
85
|
+
try {
|
|
86
|
+
localStorage.clear()
|
|
87
|
+
sessionStorage.clear()
|
|
88
|
+
} catch {
|
|
89
|
+
// Ignore
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Ignore
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test.describe('Vue Automations Example - Auth', () => {
|
|
99
|
+
let proxyAccessible = false
|
|
100
|
+
|
|
101
|
+
function skipIfNoProxy() {
|
|
102
|
+
test.skip(!proxyAccessible, 'OAuth proxy not accessible')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function skipIfNoCredentials() {
|
|
106
|
+
test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
test.beforeAll(async () => {
|
|
110
|
+
proxyAccessible = await isProxyAccessible()
|
|
111
|
+
if (!proxyAccessible) {
|
|
112
|
+
console.log('OAuth proxy not accessible - OAuth tests will be skipped')
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test.afterEach(async ({ page }) => {
|
|
117
|
+
await clearAuthState(page)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('shows login button when not authenticated', async ({ page }) => {
|
|
121
|
+
await page.goto('/')
|
|
122
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
123
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
124
|
+
await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
|
|
125
|
+
await expect(page.locator('[data-testid="nav-automations"]')).not.toBeVisible()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('automations page redirects to login when not authenticated', async ({ page }) => {
|
|
129
|
+
await page.goto('/automations')
|
|
130
|
+
// Should redirect to login page
|
|
131
|
+
await expect(page.locator('[data-testid="login-title"]')).toContainText('Login')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('login button redirects to OAuth page', async ({ page }) => {
|
|
135
|
+
skipIfNoProxy()
|
|
136
|
+
skipIfNoCredentials()
|
|
137
|
+
|
|
138
|
+
await page.goto('/')
|
|
139
|
+
await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
|
|
140
|
+
|
|
141
|
+
// Click login button to go to login page
|
|
142
|
+
await page.click('[data-testid="login-button"]')
|
|
143
|
+
await page.waitForURL('/login')
|
|
144
|
+
|
|
145
|
+
// Click the OAuth login button
|
|
146
|
+
await Promise.all([
|
|
147
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
148
|
+
page.click('button:has-text("Login with Eagle Eye Networks")')
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
152
|
+
await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('complete OAuth login flow and verify landing URL', async ({ page }) => {
|
|
156
|
+
skipIfNoProxy()
|
|
157
|
+
skipIfNoCredentials()
|
|
158
|
+
|
|
159
|
+
// Verify initially not authenticated
|
|
160
|
+
await page.goto('/')
|
|
161
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
162
|
+
|
|
163
|
+
// Perform login
|
|
164
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
165
|
+
|
|
166
|
+
// Verify landing URL is the automations page
|
|
167
|
+
await expect(page).toHaveURL('http://127.0.0.1:3333/automations')
|
|
168
|
+
|
|
169
|
+
// Verify authenticated state - we're on automations page so check nav elements
|
|
170
|
+
await expect(page.locator('[data-testid="nav-automations"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
171
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible()
|
|
172
|
+
await expect(page.locator('[data-testid="nav-login"]')).not.toBeVisible()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('can view automations after login', async ({ page }) => {
|
|
176
|
+
skipIfNoProxy()
|
|
177
|
+
skipIfNoCredentials()
|
|
178
|
+
|
|
179
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
180
|
+
await expect(page.locator('[data-testid="nav-automations"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
181
|
+
|
|
182
|
+
// Verify we're on the automations page
|
|
183
|
+
await expect(page).toHaveURL('http://127.0.0.1:3333/automations')
|
|
184
|
+
|
|
185
|
+
// Wait for data to load - should see either a table or "no data" message
|
|
186
|
+
await expect(
|
|
187
|
+
page.locator('[data-testid="event-alert-rules-table"], .no-data, .loading')
|
|
188
|
+
).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
189
|
+
|
|
190
|
+
// Should not see error state
|
|
191
|
+
await expect(page.locator('.error')).not.toBeVisible()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('can switch between automation tabs', async ({ page }) => {
|
|
195
|
+
skipIfNoProxy()
|
|
196
|
+
skipIfNoCredentials()
|
|
197
|
+
|
|
198
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
199
|
+
|
|
200
|
+
// Default tab is Event Alert Rules
|
|
201
|
+
await expect(page.locator('[data-testid="tab-event-alert-rules"]')).toHaveClass(/active/)
|
|
202
|
+
|
|
203
|
+
// Click on Condition Rules tab
|
|
204
|
+
await page.click('[data-testid="tab-condition-rules"]')
|
|
205
|
+
await expect(page.locator('[data-testid="tab-condition-rules"]')).toHaveClass(/active/)
|
|
206
|
+
await expect(page.locator('[data-testid="condition-rules-content"]')).toBeVisible()
|
|
207
|
+
|
|
208
|
+
// Click on Action Rules tab
|
|
209
|
+
await page.click('[data-testid="tab-action-rules"]')
|
|
210
|
+
await expect(page.locator('[data-testid="tab-action-rules"]')).toHaveClass(/active/)
|
|
211
|
+
await expect(page.locator('[data-testid="action-rules-content"]')).toBeVisible()
|
|
212
|
+
|
|
213
|
+
// Click on Actions tab
|
|
214
|
+
await page.click('[data-testid="tab-actions"]')
|
|
215
|
+
await expect(page.locator('[data-testid="tab-actions"]')).toHaveClass(/active/)
|
|
216
|
+
await expect(page.locator('[data-testid="actions-content"]')).toBeVisible()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('action rules tab loads data without errors', async ({ page }) => {
|
|
220
|
+
skipIfNoProxy()
|
|
221
|
+
skipIfNoCredentials()
|
|
222
|
+
|
|
223
|
+
// Collect console errors
|
|
224
|
+
const consoleErrors: string[] = []
|
|
225
|
+
page.on('console', msg => {
|
|
226
|
+
if (msg.type() === 'error') {
|
|
227
|
+
consoleErrors.push(msg.text())
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
232
|
+
|
|
233
|
+
// Click on Action Rules tab
|
|
234
|
+
await page.click('[data-testid="tab-action-rules"]')
|
|
235
|
+
await expect(page.locator('[data-testid="tab-action-rules"]')).toHaveClass(/active/)
|
|
236
|
+
|
|
237
|
+
// Wait for data to load - the TEST account should have action rules data
|
|
238
|
+
const table = page.locator('[data-testid="action-rules-table"]')
|
|
239
|
+
await expect(table).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
240
|
+
|
|
241
|
+
// Verify actual data rows exist (not just the header)
|
|
242
|
+
const rows = table.locator('tbody tr')
|
|
243
|
+
const rowCount = await rows.count()
|
|
244
|
+
expect(rowCount).toBeGreaterThan(0)
|
|
245
|
+
|
|
246
|
+
// Verify no error state is shown
|
|
247
|
+
await expect(page.locator('[data-testid="action-rules-content"] .error')).not.toBeVisible()
|
|
248
|
+
|
|
249
|
+
// Check for JavaScript errors during render
|
|
250
|
+
const renderErrors = consoleErrors.filter(e =>
|
|
251
|
+
e.includes('TypeError') || e.includes('Cannot read properties')
|
|
252
|
+
)
|
|
253
|
+
expect(renderErrors).toHaveLength(0)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('condition rules tab loads data without errors', async ({ page }) => {
|
|
257
|
+
skipIfNoProxy()
|
|
258
|
+
skipIfNoCredentials()
|
|
259
|
+
|
|
260
|
+
// Collect console errors
|
|
261
|
+
const consoleErrors: string[] = []
|
|
262
|
+
page.on('console', msg => {
|
|
263
|
+
if (msg.type() === 'error') {
|
|
264
|
+
consoleErrors.push(msg.text())
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
269
|
+
|
|
270
|
+
// Click on Condition Rules tab
|
|
271
|
+
await page.click('[data-testid="tab-condition-rules"]')
|
|
272
|
+
await expect(page.locator('[data-testid="tab-condition-rules"]')).toHaveClass(/active/)
|
|
273
|
+
|
|
274
|
+
// Wait for data to load - the TEST account should have condition rules data
|
|
275
|
+
const table = page.locator('[data-testid="condition-rules-table"]')
|
|
276
|
+
await expect(table).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
277
|
+
|
|
278
|
+
// Verify actual data rows exist (not just the header)
|
|
279
|
+
const rows = table.locator('tbody tr')
|
|
280
|
+
const rowCount = await rows.count()
|
|
281
|
+
expect(rowCount).toBeGreaterThan(0)
|
|
282
|
+
|
|
283
|
+
// Verify no error state is shown
|
|
284
|
+
await expect(page.locator('[data-testid="condition-rules-content"] .error')).not.toBeVisible()
|
|
285
|
+
|
|
286
|
+
// Check for JavaScript errors during render
|
|
287
|
+
const renderErrors = consoleErrors.filter(e =>
|
|
288
|
+
e.includes('TypeError') || e.includes('Cannot read properties')
|
|
289
|
+
)
|
|
290
|
+
expect(renderErrors).toHaveLength(0)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('actions tab loads data without errors', async ({ page }) => {
|
|
294
|
+
skipIfNoProxy()
|
|
295
|
+
skipIfNoCredentials()
|
|
296
|
+
|
|
297
|
+
// Collect console errors
|
|
298
|
+
const consoleErrors: string[] = []
|
|
299
|
+
page.on('console', msg => {
|
|
300
|
+
if (msg.type() === 'error') {
|
|
301
|
+
consoleErrors.push(msg.text())
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
306
|
+
|
|
307
|
+
// Click on Actions tab
|
|
308
|
+
await page.click('[data-testid="tab-actions"]')
|
|
309
|
+
await expect(page.locator('[data-testid="tab-actions"]')).toHaveClass(/active/)
|
|
310
|
+
|
|
311
|
+
// Wait for data to load - the TEST account should have actions data
|
|
312
|
+
const table = page.locator('[data-testid="actions-table"]')
|
|
313
|
+
await expect(table).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
314
|
+
|
|
315
|
+
// Verify actual data rows exist (not just the header)
|
|
316
|
+
const rows = table.locator('tbody tr')
|
|
317
|
+
const rowCount = await rows.count()
|
|
318
|
+
expect(rowCount).toBeGreaterThan(0)
|
|
319
|
+
|
|
320
|
+
// Verify no error state is shown
|
|
321
|
+
await expect(page.locator('[data-testid="actions-content"] .error')).not.toBeVisible()
|
|
322
|
+
|
|
323
|
+
// Check for JavaScript errors during render
|
|
324
|
+
const renderErrors = consoleErrors.filter(e =>
|
|
325
|
+
e.includes('TypeError') || e.includes('Cannot read properties')
|
|
326
|
+
)
|
|
327
|
+
expect(renderErrors).toHaveLength(0)
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
test('event alert rules tab loads data without errors', async ({ page }) => {
|
|
331
|
+
skipIfNoProxy()
|
|
332
|
+
skipIfNoCredentials()
|
|
333
|
+
|
|
334
|
+
// Collect console errors
|
|
335
|
+
const consoleErrors: string[] = []
|
|
336
|
+
page.on('console', msg => {
|
|
337
|
+
if (msg.type() === 'error') {
|
|
338
|
+
consoleErrors.push(msg.text())
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
343
|
+
|
|
344
|
+
// Event alert rules is the default tab - the TEST account should have data
|
|
345
|
+
const table = page.locator('[data-testid="event-alert-rules-table"]')
|
|
346
|
+
await expect(table).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
347
|
+
|
|
348
|
+
// Verify actual data rows exist (not just the header)
|
|
349
|
+
const rows = table.locator('tbody tr')
|
|
350
|
+
const rowCount = await rows.count()
|
|
351
|
+
expect(rowCount).toBeGreaterThan(0)
|
|
352
|
+
|
|
353
|
+
// Verify no error state is shown
|
|
354
|
+
await expect(page.locator('[data-testid="event-alert-rules-content"] .error')).not.toBeVisible()
|
|
355
|
+
|
|
356
|
+
// Check for JavaScript errors during render
|
|
357
|
+
const renderErrors = consoleErrors.filter(e =>
|
|
358
|
+
e.includes('TypeError') || e.includes('Cannot read properties')
|
|
359
|
+
)
|
|
360
|
+
expect(renderErrors).toHaveLength(0)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
test('clicking a row opens modal with JSON data', async ({ page }) => {
|
|
364
|
+
skipIfNoProxy()
|
|
365
|
+
skipIfNoCredentials()
|
|
366
|
+
|
|
367
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
368
|
+
|
|
369
|
+
// Wait for event alert rules to load (default tab)
|
|
370
|
+
const table = page.locator('[data-testid="event-alert-rules-table"]')
|
|
371
|
+
await expect(table).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
372
|
+
|
|
373
|
+
// Click the first row
|
|
374
|
+
const firstRow = table.locator('tbody tr').first()
|
|
375
|
+
await firstRow.click()
|
|
376
|
+
|
|
377
|
+
// Verify modal appears
|
|
378
|
+
const modal = page.locator('[data-testid="detail-modal"]')
|
|
379
|
+
await expect(modal).toBeVisible()
|
|
380
|
+
|
|
381
|
+
// Verify modal has JSON content
|
|
382
|
+
const jsonContent = page.locator('[data-testid="modal-json"]')
|
|
383
|
+
await expect(jsonContent).toBeVisible()
|
|
384
|
+
const jsonText = await jsonContent.textContent()
|
|
385
|
+
expect(jsonText).toContain('"id"')
|
|
386
|
+
expect(jsonText).toContain('"name"')
|
|
387
|
+
|
|
388
|
+
// Close modal by clicking close button
|
|
389
|
+
await page.click('[data-testid="modal-close"]')
|
|
390
|
+
await expect(modal).not.toBeVisible()
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('modal can be closed by clicking overlay', async ({ page }) => {
|
|
394
|
+
skipIfNoProxy()
|
|
395
|
+
skipIfNoCredentials()
|
|
396
|
+
|
|
397
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
398
|
+
|
|
399
|
+
// Wait for event alert rules to load
|
|
400
|
+
const table = page.locator('[data-testid="event-alert-rules-table"]')
|
|
401
|
+
await expect(table).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
402
|
+
|
|
403
|
+
// Click the first row to open modal
|
|
404
|
+
await table.locator('tbody tr').first().click()
|
|
405
|
+
|
|
406
|
+
// Verify modal appears
|
|
407
|
+
const modal = page.locator('[data-testid="detail-modal"]')
|
|
408
|
+
await expect(modal).toBeVisible()
|
|
409
|
+
|
|
410
|
+
// Close modal by clicking overlay (outside the modal)
|
|
411
|
+
await page.click('[data-testid="modal-overlay"]', { position: { x: 10, y: 10 } })
|
|
412
|
+
await expect(modal).not.toBeVisible()
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
test('can use enabled filter', async ({ page }) => {
|
|
416
|
+
skipIfNoProxy()
|
|
417
|
+
skipIfNoCredentials()
|
|
418
|
+
|
|
419
|
+
// Go to home and check if already logged in
|
|
420
|
+
await page.goto('/')
|
|
421
|
+
await page.waitForTimeout(1000)
|
|
422
|
+
|
|
423
|
+
const isLoggedIn = await page.locator('[data-testid="nav-logout"]').isVisible()
|
|
424
|
+
|
|
425
|
+
if (!isLoggedIn) {
|
|
426
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
427
|
+
} else {
|
|
428
|
+
// Navigate to automations if already logged in
|
|
429
|
+
await page.click('[data-testid="nav-automations"]')
|
|
430
|
+
await page.waitForURL('**/automations')
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Wait for initial data load
|
|
434
|
+
await expect(
|
|
435
|
+
page.locator('[data-testid="event-alert-rules-table"], .no-data')
|
|
436
|
+
).toBeVisible({ timeout: TIMEOUTS.DATA_LOAD })
|
|
437
|
+
|
|
438
|
+
// Change filter to enabled only
|
|
439
|
+
await page.selectOption('[data-testid="enabled-filter"]', 'enabled')
|
|
440
|
+
|
|
441
|
+
// Wait for data to reload
|
|
442
|
+
await page.waitForTimeout(1000)
|
|
443
|
+
|
|
444
|
+
// Should still show content (table or no-data) without error
|
|
445
|
+
await expect(page.locator('.error')).not.toBeVisible()
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
test('can logout after login', async ({ page }) => {
|
|
449
|
+
skipIfNoProxy()
|
|
450
|
+
skipIfNoCredentials()
|
|
451
|
+
|
|
452
|
+
// Always perform login to ensure test isolation
|
|
453
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
454
|
+
|
|
455
|
+
// Go to home to see the logout button
|
|
456
|
+
await page.goto('/')
|
|
457
|
+
|
|
458
|
+
const logoutButton = page.locator('[data-testid="nav-logout"]')
|
|
459
|
+
await expect(logoutButton).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
460
|
+
|
|
461
|
+
await logoutButton.click()
|
|
462
|
+
|
|
463
|
+
// Wait for logout to complete and redirect to home
|
|
464
|
+
await page.waitForURL('**/', { timeout: TIMEOUTS.AUTH_COMPLETE })
|
|
465
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
466
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
467
|
+
})
|
|
468
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>EEN Automations Example</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|