een-api-toolkit 0.3.47 → 0.3.49
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/een-jobs-agent.md +676 -0
- package/CHANGELOG.md +7 -8
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1172 -28
- package/dist/index.js +796 -333
- package/dist/index.js.map +1 -1
- package/docs/AI-CONTEXT.md +22 -1
- package/docs/ai-reference/AI-AUTH.md +1 -1
- package/docs/ai-reference/AI-AUTOMATIONS.md +1 -1
- 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 +1 -1
- package/docs/ai-reference/AI-JOBS.md +1084 -0
- 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-jobs/.env.example +11 -0
- package/examples/vue-jobs/README.md +245 -0
- package/examples/vue-jobs/e2e/app.spec.ts +79 -0
- package/examples/vue-jobs/e2e/auth.spec.ts +382 -0
- package/examples/vue-jobs/e2e/delete-features.spec.ts +564 -0
- package/examples/vue-jobs/e2e/timelapse.spec.ts +361 -0
- package/examples/vue-jobs/index.html +13 -0
- package/examples/vue-jobs/package-lock.json +1722 -0
- package/examples/vue-jobs/package.json +28 -0
- package/examples/vue-jobs/playwright.config.ts +47 -0
- package/examples/vue-jobs/src/App.vue +154 -0
- package/examples/vue-jobs/src/main.ts +25 -0
- package/examples/vue-jobs/src/router/index.ts +82 -0
- package/examples/vue-jobs/src/views/Callback.vue +76 -0
- package/examples/vue-jobs/src/views/CreateExport.vue +284 -0
- package/examples/vue-jobs/src/views/Files.vue +424 -0
- package/examples/vue-jobs/src/views/Home.vue +195 -0
- package/examples/vue-jobs/src/views/JobDetail.vue +392 -0
- package/examples/vue-jobs/src/views/Jobs.vue +297 -0
- package/examples/vue-jobs/src/views/Login.vue +33 -0
- package/examples/vue-jobs/src/views/Logout.vue +59 -0
- package/examples/vue-jobs/src/vite-env.d.ts +1 -0
- package/examples/vue-jobs/tsconfig.json +25 -0
- package/examples/vue-jobs/vite.config.ts +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { test, expect, Page } from '@playwright/test'
|
|
2
|
+
import { baseURL } from '../playwright.config'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* E2E tests for the Vue Jobs Example
|
|
6
|
+
*
|
|
7
|
+
* Tests the OAuth login flow through the UI:
|
|
8
|
+
* 1. Click login button in the example app
|
|
9
|
+
* 2. Enter credentials on EEN OAuth page
|
|
10
|
+
* 3. Complete the OAuth callback
|
|
11
|
+
* 4. Verify authenticated state
|
|
12
|
+
*
|
|
13
|
+
* Required environment variables:
|
|
14
|
+
* - VITE_PROXY_URL: OAuth proxy URL (e.g., http://127.0.0.1:8787)
|
|
15
|
+
* - VITE_EEN_CLIENT_ID: EEN OAuth client ID
|
|
16
|
+
* - TEST_USER: Test user email
|
|
17
|
+
* - TEST_PASSWORD: Test user password
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Timeout constants for consistent behavior
|
|
21
|
+
// Values chosen based on OAuth flow timing requirements
|
|
22
|
+
const TIMEOUTS = {
|
|
23
|
+
OAUTH_REDIRECT: 30000, // OAuth redirects can be slow on first load
|
|
24
|
+
ELEMENT_VISIBLE: 15000, // Wait for OAuth page elements to render
|
|
25
|
+
PASSWORD_VISIBLE: 10000, // Password field appears after email validation
|
|
26
|
+
AUTH_COMPLETE: 30000, // Full OAuth flow completion
|
|
27
|
+
UI_UPDATE: 10000, // UI state updates after auth changes
|
|
28
|
+
PROXY_CHECK: 5000 // Quick check if proxy is running
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
const TEST_USER = process.env.TEST_USER
|
|
32
|
+
const TEST_PASSWORD = process.env.TEST_PASSWORD
|
|
33
|
+
const PROXY_URL = process.env.VITE_PROXY_URL
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if the OAuth proxy is accessible.
|
|
37
|
+
* Returns true if proxy responds (even with 404), false if unreachable.
|
|
38
|
+
*/
|
|
39
|
+
async function isProxyAccessible(): Promise<boolean> {
|
|
40
|
+
if (!PROXY_URL) return false
|
|
41
|
+
const controller = new AbortController()
|
|
42
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUTS.PROXY_CHECK)
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(PROXY_URL, {
|
|
46
|
+
method: 'HEAD',
|
|
47
|
+
signal: controller.signal
|
|
48
|
+
})
|
|
49
|
+
// 404 is ok - means proxy is running but endpoint doesn't exist
|
|
50
|
+
return response.ok || response.status === 404
|
|
51
|
+
} catch {
|
|
52
|
+
return false
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timeoutId)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Performs OAuth login flow through the UI.
|
|
60
|
+
* Starts from home page and completes full OAuth authentication.
|
|
61
|
+
*/
|
|
62
|
+
async function performLogin(page: Page, username: string, password: string): Promise<void> {
|
|
63
|
+
// Start at home page
|
|
64
|
+
await page.goto('/')
|
|
65
|
+
|
|
66
|
+
// Click login button and wait for OAuth redirect
|
|
67
|
+
await Promise.all([
|
|
68
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
69
|
+
page.click('[data-testid="login-button"]')
|
|
70
|
+
])
|
|
71
|
+
|
|
72
|
+
// Fill email
|
|
73
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
74
|
+
await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
75
|
+
await emailInput.fill(username)
|
|
76
|
+
|
|
77
|
+
// Click next
|
|
78
|
+
await page.getByRole('button', { name: 'Next' }).click()
|
|
79
|
+
|
|
80
|
+
// Fill password
|
|
81
|
+
const passwordInput = page.locator('#authentication--input__password')
|
|
82
|
+
await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
|
|
83
|
+
await passwordInput.fill(password)
|
|
84
|
+
|
|
85
|
+
// Click sign in - use OR selector for robustness
|
|
86
|
+
await page.locator('#next, button:has-text("Sign in")').first().click()
|
|
87
|
+
|
|
88
|
+
// Wait for redirect back to the app using configured baseURL
|
|
89
|
+
const baseURLPattern = new RegExp(baseURL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
90
|
+
await page.waitForURL(baseURLPattern, { timeout: TIMEOUTS.AUTH_COMPLETE })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clears browser storage to reset auth state.
|
|
95
|
+
* Handles cases where localStorage isn't accessible (e.g., about:blank, cross-origin).
|
|
96
|
+
*/
|
|
97
|
+
async function clearAuthState(page: Page): Promise<void> {
|
|
98
|
+
try {
|
|
99
|
+
// Only try to clear storage if we're on a page that allows it
|
|
100
|
+
const url = page.url()
|
|
101
|
+
if (url && url.startsWith('http')) {
|
|
102
|
+
await page.evaluate(() => {
|
|
103
|
+
try {
|
|
104
|
+
localStorage.clear()
|
|
105
|
+
sessionStorage.clear()
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore errors - storage may not be accessible
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore errors - page may be closed or in an inaccessible state
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
test.describe('Vue Jobs Example', () => {
|
|
117
|
+
// Check proxy accessibility once before all tests
|
|
118
|
+
let proxyAccessible = false
|
|
119
|
+
|
|
120
|
+
// Helper functions to skip tests when prerequisites aren't met
|
|
121
|
+
function skipIfNoProxy() {
|
|
122
|
+
test.skip(!proxyAccessible, 'OAuth proxy not accessible')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function skipIfNoCredentials() {
|
|
126
|
+
test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function skipIfNoUser() {
|
|
130
|
+
test.skip(!TEST_USER, 'Test user not available')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
test.beforeAll(async () => {
|
|
134
|
+
proxyAccessible = await isProxyAccessible()
|
|
135
|
+
if (!proxyAccessible) {
|
|
136
|
+
console.log('OAuth proxy not accessible - OAuth tests will be skipped')
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test.afterEach(async ({ page }) => {
|
|
141
|
+
// Clear auth state after each test to prevent state pollution
|
|
142
|
+
await clearAuthState(page)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('shows login button when not authenticated', async ({ page }) => {
|
|
146
|
+
await page.goto('/')
|
|
147
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
148
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
149
|
+
await expect(page.locator('[data-testid="nav-logout"]')).not.toBeVisible()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('jobs page redirects to login without authentication', async ({ page }) => {
|
|
153
|
+
await page.goto('/jobs')
|
|
154
|
+
await page.waitForURL('/login')
|
|
155
|
+
await expect(page.locator('[data-testid="login-title"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('login button redirects to OAuth page', async ({ page }) => {
|
|
159
|
+
skipIfNoProxy()
|
|
160
|
+
skipIfNoCredentials()
|
|
161
|
+
|
|
162
|
+
await page.goto('/')
|
|
163
|
+
await expect(page.locator('[data-testid="login-button"]')).toBeVisible()
|
|
164
|
+
await expect(page.locator('[data-testid="login-button"]')).toBeEnabled()
|
|
165
|
+
|
|
166
|
+
// Click login and verify redirect to OAuth page
|
|
167
|
+
await Promise.all([
|
|
168
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
169
|
+
page.click('[data-testid="login-button"]')
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
// Verify we're on the OAuth page
|
|
173
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
174
|
+
await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('complete OAuth login flow', async ({ page }) => {
|
|
178
|
+
skipIfNoProxy()
|
|
179
|
+
skipIfNoCredentials()
|
|
180
|
+
|
|
181
|
+
// Verify initially not authenticated
|
|
182
|
+
await page.goto('/')
|
|
183
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible()
|
|
184
|
+
|
|
185
|
+
// Perform login
|
|
186
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
187
|
+
|
|
188
|
+
// Verify authenticated state
|
|
189
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).not.toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
190
|
+
await expect(page.locator('[data-testid="nav-jobs"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
191
|
+
await expect(page.locator('[data-testid="nav-files"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
192
|
+
await expect(page.locator('[data-testid="nav-create-export"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
193
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
194
|
+
await expect(page.locator('[data-testid="nav-login"]')).not.toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('can view jobs list after login', async ({ page }) => {
|
|
198
|
+
skipIfNoProxy()
|
|
199
|
+
skipIfNoCredentials()
|
|
200
|
+
|
|
201
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
202
|
+
await expect(page.locator('[data-testid="nav-jobs"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
203
|
+
|
|
204
|
+
// Navigate to jobs page
|
|
205
|
+
await page.click('[data-testid="nav-jobs"]')
|
|
206
|
+
await page.waitForURL('/jobs')
|
|
207
|
+
|
|
208
|
+
// Should see jobs content (table or "No jobs found" message)
|
|
209
|
+
await expect(
|
|
210
|
+
page.locator('.jobs table, .jobs p:has-text("No jobs found")').first()
|
|
211
|
+
).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
212
|
+
await expect(page.locator('.error')).not.toBeVisible()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('can view files list after login', async ({ page }) => {
|
|
216
|
+
skipIfNoProxy()
|
|
217
|
+
skipIfNoCredentials()
|
|
218
|
+
|
|
219
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
220
|
+
await expect(page.locator('[data-testid="nav-files"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
221
|
+
|
|
222
|
+
// Navigate to files page
|
|
223
|
+
await page.click('[data-testid="nav-files"]')
|
|
224
|
+
await page.waitForURL('/files')
|
|
225
|
+
|
|
226
|
+
// Should see files content (table or "No files found" message)
|
|
227
|
+
await expect(
|
|
228
|
+
page.locator('.files table, .files p:has-text("No files found")').first()
|
|
229
|
+
).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('files list displays valid data (type, size, date)', async ({ page }) => {
|
|
233
|
+
skipIfNoProxy()
|
|
234
|
+
skipIfNoCredentials()
|
|
235
|
+
|
|
236
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
237
|
+
await page.click('[data-testid="nav-files"]')
|
|
238
|
+
await page.waitForURL('/files')
|
|
239
|
+
|
|
240
|
+
// Wait for either loading to finish or content to appear
|
|
241
|
+
// First wait for loading state to clear
|
|
242
|
+
await page.waitForFunction(
|
|
243
|
+
() => !document.querySelector('.files .loading'),
|
|
244
|
+
{ timeout: TIMEOUTS.ELEMENT_VISIBLE }
|
|
245
|
+
).catch(() => {})
|
|
246
|
+
|
|
247
|
+
// Wait for files table to be visible
|
|
248
|
+
const table = page.locator('.files table')
|
|
249
|
+
const hasTable = await table.isVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE }).catch(() => false)
|
|
250
|
+
|
|
251
|
+
if (!hasTable) {
|
|
252
|
+
// Check if "No files found" is shown
|
|
253
|
+
const noFiles = page.locator('.files p:has-text("No files found")')
|
|
254
|
+
const hasNoFiles = await noFiles.isVisible({ timeout: 2000 }).catch(() => false)
|
|
255
|
+
if (hasNoFiles) {
|
|
256
|
+
console.log('No files found message visible - skipping data validation')
|
|
257
|
+
} else {
|
|
258
|
+
console.log('No files table or message visible - skipping data validation')
|
|
259
|
+
}
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Get all data rows
|
|
264
|
+
const rows = table.locator('tbody tr')
|
|
265
|
+
const rowCount = await rows.count()
|
|
266
|
+
console.log(`Found ${rowCount} files in the table`)
|
|
267
|
+
|
|
268
|
+
// Validate each row has proper metadata
|
|
269
|
+
let foundFileWithSize = false
|
|
270
|
+
for (let i = 0; i < Math.min(rowCount, 10); i++) {
|
|
271
|
+
const row = rows.nth(i)
|
|
272
|
+
const cells = row.locator('td')
|
|
273
|
+
const name = await cells.nth(0).textContent()
|
|
274
|
+
const type = await cells.nth(1).textContent()
|
|
275
|
+
const size = await cells.nth(2).textContent()
|
|
276
|
+
const date = await cells.nth(3).textContent()
|
|
277
|
+
|
|
278
|
+
console.log(`Row ${i + 1}:`, { name: name?.substring(0, 40), type, size, date: date?.substring(0, 20) })
|
|
279
|
+
|
|
280
|
+
// Validate name is not empty
|
|
281
|
+
expect(name).toBeTruthy()
|
|
282
|
+
expect(name!.trim().length).toBeGreaterThan(0)
|
|
283
|
+
|
|
284
|
+
// Validate type is not empty (folder, video, image, etc.)
|
|
285
|
+
expect(type).toBeTruthy()
|
|
286
|
+
expect(type!.trim().length).toBeGreaterThan(0)
|
|
287
|
+
|
|
288
|
+
// Validate size format: "-" for folders, or valid size (e.g., "1.5 MB")
|
|
289
|
+
expect(size).toBeTruthy()
|
|
290
|
+
const sizeValue = size!.trim()
|
|
291
|
+
expect(sizeValue).toMatch(/^(-|\d+(\.\d+)?\s*(B|KB|MB|GB))$/)
|
|
292
|
+
|
|
293
|
+
// Track if we found a file with actual size (not a folder)
|
|
294
|
+
if (sizeValue !== '-') {
|
|
295
|
+
foundFileWithSize = true
|
|
296
|
+
console.log(` -> Found file with size: ${sizeValue}`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Validate date is valid (not "Invalid Date")
|
|
300
|
+
expect(date).toBeTruthy()
|
|
301
|
+
expect(date!.trim()).not.toBe('Invalid Date')
|
|
302
|
+
expect(date!.trim().length).toBeGreaterThan(0)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Ensure we found at least one file with actual size data
|
|
306
|
+
// This validates the size field is being returned from the API
|
|
307
|
+
if (!foundFileWithSize && rowCount > 0) {
|
|
308
|
+
console.log('Note: All visible files are folders (no size). This is valid but size display not fully verified.')
|
|
309
|
+
} else if (foundFileWithSize) {
|
|
310
|
+
console.log('✓ Successfully verified file size display')
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('can access create export page after login', async ({ page }) => {
|
|
315
|
+
skipIfNoProxy()
|
|
316
|
+
skipIfNoCredentials()
|
|
317
|
+
|
|
318
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
319
|
+
await expect(page.locator('[data-testid="nav-create-export"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
320
|
+
|
|
321
|
+
// Navigate to create export page
|
|
322
|
+
await page.click('[data-testid="nav-create-export"]')
|
|
323
|
+
await page.waitForURL('/create-export')
|
|
324
|
+
|
|
325
|
+
// Should see the create export form or loading state
|
|
326
|
+
await expect(
|
|
327
|
+
page.locator('.create-export h2:has-text("Create Export")').first()
|
|
328
|
+
).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
test('can logout after login', async ({ page }) => {
|
|
332
|
+
skipIfNoProxy()
|
|
333
|
+
skipIfNoCredentials()
|
|
334
|
+
|
|
335
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
336
|
+
await expect(page.locator('[data-testid="nav-logout"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
337
|
+
|
|
338
|
+
// Click logout
|
|
339
|
+
await page.click('[data-testid="nav-logout"]')
|
|
340
|
+
|
|
341
|
+
// Should show not authenticated - wait for redirect to app baseURL
|
|
342
|
+
const baseURLPattern = new RegExp(baseURL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
343
|
+
await page.waitForURL(baseURLPattern)
|
|
344
|
+
await expect(page.locator('[data-testid="not-authenticated"]')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
345
|
+
await expect(page.locator('[data-testid="nav-login"]')).toBeVisible()
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('invalid password shows error on OAuth page', async ({ page }) => {
|
|
349
|
+
skipIfNoProxy()
|
|
350
|
+
skipIfNoUser()
|
|
351
|
+
|
|
352
|
+
await page.goto('/')
|
|
353
|
+
|
|
354
|
+
// Click login and wait for OAuth redirect
|
|
355
|
+
await Promise.all([
|
|
356
|
+
page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
|
|
357
|
+
page.click('[data-testid="login-button"]')
|
|
358
|
+
])
|
|
359
|
+
|
|
360
|
+
// Fill valid email
|
|
361
|
+
const emailInput = page.locator('#authentication--input__email')
|
|
362
|
+
await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
|
|
363
|
+
await emailInput.fill(TEST_USER!)
|
|
364
|
+
await page.getByRole('button', { name: 'Next' }).click()
|
|
365
|
+
|
|
366
|
+
// Fill invalid password
|
|
367
|
+
const passwordInput = page.locator('#authentication--input__password')
|
|
368
|
+
await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
|
|
369
|
+
await passwordInput.fill('invalid-password-12345!')
|
|
370
|
+
|
|
371
|
+
// Click sign in
|
|
372
|
+
await page.locator('#next, button:has-text("Sign in")').first().click()
|
|
373
|
+
|
|
374
|
+
// Should show error message on OAuth page
|
|
375
|
+
await expect(
|
|
376
|
+
page.locator('.error, [class*="error"], [data-testid*="error"], #error, .alert-danger').first()
|
|
377
|
+
).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
378
|
+
|
|
379
|
+
// Should still be on OAuth page
|
|
380
|
+
await expect(page).toHaveURL(/eagleeyenetworks\.com/)
|
|
381
|
+
})
|
|
382
|
+
})
|