een-api-toolkit 0.1.13 → 0.2.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.
@@ -1,510 +1,248 @@
1
1
  /**
2
- * Authenticated E2E tests for vue-feeds example
2
+ * E2E tests for the Vue Feeds Example - OAuth Login Flow
3
3
  *
4
- * These tests require:
5
- * - TEST_USER and TEST_PASSWORD environment variables
6
- * - VITE_EEN_CLIENT_ID environment variable
7
- * - Running OAuth proxy server (see ../../../scripts/restart-proxy.sh)
4
+ * Tests the OAuth login flow through the UI:
5
+ * 1. Click login button in the example app
6
+ * 2. Enter credentials on EEN OAuth page
7
+ * 3. Complete the OAuth callback
8
+ * 4. Verify authenticated state and feeds functionality
9
+ *
10
+ * Required environment variables:
11
+ * - VITE_PROXY_URL: OAuth proxy URL (e.g., http://127.0.0.1:8787)
12
+ * - VITE_EEN_CLIENT_ID: EEN OAuth client ID
13
+ * - TEST_USER: Test user email
14
+ * - TEST_PASSWORD: Test user password
15
+ *
16
+ * Note: Helper functions (isProxyAccessible, performLogin, clearAuthState) are
17
+ * intentionally duplicated in each example's auth.spec.ts to avoid Playwright's
18
+ * "Requiring @playwright/test second time" error that occurs when importing
19
+ * from a shared file outside the example directory.
8
20
  */
9
21
 
10
- import { test, expect } from '@playwright/test'
11
- import { getAuthToken, injectAuthState, AuthState } from '../../../e2e/auth-helper'
22
+ import { test, expect, Page } from '@playwright/test'
23
+ import { baseURL } from '../playwright.config'
12
24
 
13
- test.describe('vue-feeds authenticated tests', () => {
14
- let authState: AuthState
25
+ const TIMEOUTS = {
26
+ OAUTH_REDIRECT: 30000,
27
+ ELEMENT_VISIBLE: 15000,
28
+ PASSWORD_VISIBLE: 10000,
29
+ AUTH_COMPLETE: 30000,
30
+ UI_UPDATE: 10000,
31
+ PROXY_CHECK: 5000,
32
+ FEEDS_LOAD: 30000
33
+ } as const
15
34
 
16
- test.beforeAll(async () => {
17
- // Get auth token (from cache or fresh login)
18
- authState = await getAuthToken()
19
- })
35
+ const TEST_USER = process.env.TEST_USER
36
+ const TEST_PASSWORD = process.env.TEST_PASSWORD
37
+ const PROXY_URL = process.env.VITE_PROXY_URL
20
38
 
21
- test.beforeEach(async ({ page }) => {
22
- // Navigate to home and inject auth state before each test
23
- await page.goto('/')
24
- await injectAuthState(page, authState)
25
- })
26
-
27
- test('authenticated home page shows view feeds button', async ({ page }) => {
28
- // Reload to apply auth state (already injected by beforeEach)
29
- await page.reload()
30
-
31
- // Should show authenticated state
32
- await expect(page.getByTestId('authenticated')).toBeVisible()
33
- await expect(page.getByTestId('view-feeds-button')).toBeVisible()
34
- await expect(page.getByText('You are logged in!')).toBeVisible()
35
- })
36
-
37
- test('authenticated user sees cameras on feeds page', async ({ page }) => {
38
- // Navigate to feeds page (auth already injected by beforeEach)
39
- await page.goto('/feeds')
40
-
41
- // Should show the feeds view
42
- await expect(page.getByRole('heading', { name: 'Camera Feeds' })).toBeVisible()
43
-
44
- // Wait for cameras to load (or show no cameras message)
45
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
46
- timeout: 30000
39
+ /**
40
+ * Checks if the OAuth proxy server is accessible.
41
+ * Returns false if proxy is unavailable, allowing tests to be skipped gracefully.
42
+ */
43
+ async function isProxyAccessible(): Promise<boolean> {
44
+ if (!PROXY_URL) return false
45
+ const controller = new AbortController()
46
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUTS.PROXY_CHECK)
47
+
48
+ try {
49
+ const response = await fetch(PROXY_URL, {
50
+ method: 'HEAD',
51
+ signal: controller.signal
47
52
  })
48
-
49
- // Either cameras are loaded or we see "no cameras" message
50
- const cameraSelect = page.getByTestId('camera-select')
51
- const noCameras = page.locator('.no-cameras')
52
-
53
- const hasCameras = await cameraSelect.isVisible().catch(() => false)
54
- const hasNoCameras = await noCameras.isVisible().catch(() => false)
55
-
56
- expect(hasCameras || hasNoCameras).toBe(true)
57
-
58
- if (hasCameras) {
59
- console.log('Cameras found - checking controls')
60
-
61
- // Verify controls are present
62
- await expect(page.getByTestId('refresh-button')).toBeVisible()
63
-
64
- // Wait for feeds to load
65
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
66
- timeout: 30000
67
- })
68
-
69
- // Check if we have feeds
70
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
71
- if (hasFeeds) {
72
- console.log('Feeds loaded successfully')
73
-
74
- // Check feeds summary
75
- await expect(page.getByTestId('feeds-summary')).toBeVisible()
76
-
77
- // Check that at least one feed row exists
78
- const feedRows = page.getByTestId('feed-row')
79
- const count = await feedRows.count()
80
- expect(count).toBeGreaterThan(0)
81
- console.log(`Found ${count} feeds`)
82
- } else {
83
- console.log('No feeds available for this camera')
84
- }
85
- } else {
86
- console.log('No cameras in account')
53
+ return response.ok || response.status === 404
54
+ } catch (error) {
55
+ if (!process.env.CI) {
56
+ console.log('Proxy check failed:', error instanceof Error ? error.message : error)
87
57
  }
88
- })
58
+ return false
59
+ } finally {
60
+ clearTimeout(timeoutId)
61
+ }
62
+ }
89
63
 
90
- test('feeds table shows correct feed information', async ({ page }) => {
91
- // Navigate to feeds page (auth already injected by beforeEach)
92
- await page.goto('/feeds')
64
+ /**
65
+ * Performs OAuth login flow through the EEN authentication page.
66
+ * Handles two-step navigation: app login page -> EEN OAuth -> callback
67
+ */
68
+ async function performLogin(page: Page, username: string, password: string): Promise<void> {
69
+ await page.goto('/')
93
70
 
94
- // Wait for cameras to load
95
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
96
- timeout: 30000
97
- })
71
+ // Click login button on home page to go to login page
72
+ await page.click('[data-testid="login-button"]')
73
+ await page.waitForURL('/login')
98
74
 
99
- const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
75
+ // Click login button on login page to trigger OAuth
76
+ await Promise.all([
77
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
78
+ page.getByRole('button', { name: 'Login with Eagle Eye Networks' }).click()
79
+ ])
100
80
 
101
- if (hasCameras) {
102
- // Wait for feeds to load
103
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
104
- timeout: 30000
105
- })
81
+ // EEN OAuth page selectors - these depend on EEN's login UI and may need
82
+ // updates if EEN changes their authentication page structure
83
+ const emailInput = page.locator('#authentication--input__email')
84
+ await emailInput.waitFor({ state: 'visible', timeout: TIMEOUTS.ELEMENT_VISIBLE })
85
+ await emailInput.fill(username)
106
86
 
107
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
108
-
109
- if (hasFeeds) {
110
- // Verify feed ID is displayed
111
- const feedId = await page.getByTestId('feed-id').first().textContent()
112
- expect(feedId).toBeTruthy()
113
- console.log(`First feed ID: ${feedId}`)
114
-
115
- // Verify feed type is displayed (main, preview, or talkdown)
116
- const feedType = await page.getByTestId('feed-type').first().textContent()
117
- expect(feedType).toMatch(/main|preview|talkdown/i)
118
- console.log(`First feed type: ${feedType}`)
119
-
120
- // Verify media type is displayed
121
- const mediaType = await page.getByTestId('feed-media-type').first().textContent()
122
- expect(mediaType).toBeTruthy()
123
- console.log(`First feed media type: ${mediaType}`)
124
-
125
- // Verify URL availability is shown
126
- const feedUrls = page.getByTestId('feed-urls').first()
127
- await expect(feedUrls).toBeVisible()
128
- } else {
129
- console.log('No feeds to test')
130
- }
131
- } else {
132
- console.log('No cameras to test with')
133
- }
134
- })
87
+ await page.getByRole('button', { name: 'Next' }).click()
135
88
 
136
- test('refresh button fetches feeds', async ({ page }) => {
137
- // Navigate to feeds page (auth already injected by beforeEach)
138
- await page.goto('/feeds')
89
+ const passwordInput = page.locator('#authentication--input__password')
90
+ await passwordInput.waitFor({ state: 'visible', timeout: TIMEOUTS.PASSWORD_VISIBLE })
91
+ await passwordInput.fill(password)
139
92
 
140
- // Wait for cameras to load
141
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
142
- timeout: 30000
143
- })
93
+ // EEN uses either #next or "Sign in" button depending on login flow
94
+ await page.locator('#next, button:has-text("Sign in")').first().click()
144
95
 
145
- const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
96
+ // Wait for redirect back to the app using configured baseURL
97
+ const baseURLPattern = new RegExp(baseURL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
98
+ await page.waitForURL(baseURLPattern, { timeout: TIMEOUTS.AUTH_COMPLETE })
99
+ }
146
100
 
147
- if (hasCameras) {
148
- // Wait for feeds to load
149
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
150
- timeout: 30000
101
+ /**
102
+ * Clears authentication state from browser storage.
103
+ * Used in afterEach to ensure test isolation.
104
+ */
105
+ async function clearAuthState(page: Page): Promise<void> {
106
+ try {
107
+ const url = page.url()
108
+ if (url && url.startsWith('http')) {
109
+ await page.evaluate(() => {
110
+ try {
111
+ localStorage.clear()
112
+ sessionStorage.clear()
113
+ } catch {
114
+ // Storage access may fail in certain contexts
115
+ }
151
116
  })
152
-
153
- // Click refresh
154
- await page.getByTestId('refresh-button').click()
155
-
156
- // Button should be visible (not broken)
157
- await expect(page.getByTestId('refresh-button')).toBeVisible()
158
-
159
- console.log('Refresh button clicked successfully')
160
- } else {
161
- console.log('No cameras to test refresh with')
162
117
  }
163
- })
164
-
165
- test('camera selection changes feeds', async ({ page }) => {
166
- // Navigate to feeds page (auth already injected by beforeEach)
167
- await page.goto('/feeds')
168
-
169
- // Wait for cameras to load
170
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
171
- timeout: 30000
172
- })
173
-
174
- const cameraSelect = page.getByTestId('camera-select')
175
- const hasCameras = await cameraSelect.isVisible().catch(() => false)
176
-
177
- if (hasCameras) {
178
- // Get all camera options
179
- const options = await cameraSelect.locator('option').all()
180
-
181
- if (options.length >= 2) {
182
- // Wait for initial feeds to load
183
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
184
- timeout: 30000
185
- })
186
-
187
- // Get the second camera's value
188
- const secondCameraValue = await options[1].getAttribute('value')
189
- console.log(`Selecting camera: ${secondCameraValue}`)
190
-
191
- // Select the second camera
192
- await cameraSelect.selectOption(secondCameraValue!)
118
+ } catch (error) {
119
+ if (!process.env.CI) {
120
+ console.log('Clear auth state failed:', error instanceof Error ? error.message : error)
121
+ }
122
+ }
123
+ }
193
124
 
194
- // Wait for feeds to reload
195
- await page.waitForSelector('[data-testid="feeds-list"]', {
196
- timeout: 30000
197
- })
125
+ test.describe('Vue Feeds Example - Auth', () => {
126
+ let proxyAccessible = false
198
127
 
199
- // Give it a moment to fetch new feeds
200
- await page.waitForTimeout(2000)
128
+ function skipIfNoProxy() {
129
+ test.skip(!proxyAccessible, 'OAuth proxy not accessible')
130
+ }
201
131
 
202
- // Verify feeds list is visible
203
- await expect(page.getByTestId('feeds-list')).toBeVisible()
132
+ function skipIfNoCredentials() {
133
+ test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
134
+ }
204
135
 
205
- console.log('Camera selection changed successfully')
206
- } else {
207
- console.log('Only one camera available - cannot test camera switching')
208
- }
209
- } else {
210
- console.log('No cameras to test with')
136
+ test.beforeAll(async () => {
137
+ proxyAccessible = await isProxyAccessible()
138
+ if (!proxyAccessible) {
139
+ console.log('OAuth proxy not accessible - OAuth tests will be skipped')
211
140
  }
212
141
  })
213
142
 
214
- test('preview button opens modal with multipart image', async ({ page }) => {
215
- // Navigate to feeds page (auth already injected by beforeEach)
216
- await page.goto('/feeds')
217
-
218
- // Wait for cameras to load
219
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
220
- timeout: 30000
221
- })
222
-
223
- const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
224
-
225
- if (hasCameras) {
226
- // Wait for feeds to load
227
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
228
- timeout: 30000
229
- })
230
-
231
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
232
-
233
- if (hasFeeds) {
234
- // Check if there's a View button (for preview feeds)
235
- const viewButton = page.getByTestId('view-preview-button').first()
236
- const hasViewButton = await viewButton.isVisible().catch(() => false)
237
-
238
- if (hasViewButton) {
239
- console.log('Found View button - clicking to open modal')
240
-
241
- // Click the View button
242
- await viewButton.click()
243
-
244
- // Wait for modal to appear
245
- await expect(page.getByTestId('preview-modal')).toBeVisible({ timeout: 10000 })
246
-
247
- // Verify modal shows preview image
248
- await expect(page.getByTestId('preview-image')).toBeVisible({ timeout: 10000 })
249
-
250
- // Verify modal header shows "Live Preview"
251
- await expect(page.getByRole('heading', { name: 'Live Preview' })).toBeVisible()
252
-
253
- // Verify mode badge shows PREVIEW
254
- await expect(page.locator('.mode-preview')).toContainText('PREVIEW')
255
-
256
- console.log('Preview modal opened successfully with multipart image')
143
+ test.afterEach(async ({ page }) => {
144
+ await clearAuthState(page)
145
+ })
257
146
 
258
- // Close modal
259
- await page.locator('.close-button').click()
260
- await expect(page.getByTestId('preview-modal')).not.toBeVisible()
261
- console.log('Modal closed successfully')
262
- } else {
263
- console.log('No preview feeds with View button available')
264
- }
265
- } else {
266
- console.log('No feeds available to test preview modal')
267
- }
268
- } else {
269
- console.log('No cameras to test with')
270
- }
147
+ test('shows login button when not authenticated', async ({ page }) => {
148
+ await page.goto('/')
149
+ await expect(page.getByTestId('not-authenticated')).toBeVisible()
150
+ await expect(page.getByTestId('nav-login')).toBeVisible()
271
151
  })
272
152
 
273
- test('live button opens modal with video element', async ({ page }) => {
274
- // Navigate to feeds page (auth already injected by beforeEach)
153
+ test('feeds page redirects to login when not authenticated', async ({ page }) => {
275
154
  await page.goto('/feeds')
155
+ await expect(page).toHaveURL('/login')
156
+ })
276
157
 
277
- // Wait for cameras to load
278
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
279
- timeout: 30000
280
- })
281
-
282
- const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
158
+ test('login button redirects to OAuth page', async ({ page }) => {
159
+ skipIfNoProxy()
160
+ skipIfNoCredentials()
283
161
 
284
- if (hasCameras) {
285
- // Wait for feeds to load
286
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
287
- timeout: 30000
288
- })
162
+ await page.goto('/')
163
+ await expect(page.getByTestId('login-button')).toBeVisible()
289
164
 
290
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
165
+ // Click login button on home page to go to login page
166
+ await page.click('[data-testid="login-button"]')
167
+ await page.waitForURL('/login')
291
168
 
292
- if (hasFeeds) {
293
- // Check if there's a Live button (for main feeds)
294
- const liveButton = page.getByTestId('view-live-button').first()
295
- const hasLiveButton = await liveButton.isVisible().catch(() => false)
169
+ // Click login button on login page to trigger OAuth
170
+ await Promise.all([
171
+ page.waitForURL(/.*eagleeyenetworks.com.*/, { timeout: TIMEOUTS.OAUTH_REDIRECT }),
172
+ page.getByRole('button', { name: 'Login with Eagle Eye Networks' }).click()
173
+ ])
296
174
 
297
- if (hasLiveButton) {
298
- console.log('Found Live button - clicking to open modal')
175
+ // EEN OAuth page selector
176
+ const emailInput = page.locator('#authentication--input__email')
177
+ await expect(emailInput).toBeVisible({ timeout: TIMEOUTS.ELEMENT_VISIBLE })
178
+ })
299
179
 
300
- // Click the Live button
301
- await liveButton.click()
180
+ test('complete OAuth login flow and view feeds', async ({ page }) => {
181
+ skipIfNoProxy()
182
+ skipIfNoCredentials()
302
183
 
303
- // Wait for modal to appear
304
- await expect(page.getByTestId('preview-modal')).toBeVisible({ timeout: 10000 })
184
+ await page.goto('/')
185
+ await expect(page.getByTestId('not-authenticated')).toBeVisible()
305
186
 
306
- // Verify modal shows video element
307
- await expect(page.getByTestId('preview-video')).toBeVisible({ timeout: 10000 })
187
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
308
188
 
309
- // Verify modal header shows "Live Stream (SDK)"
310
- await expect(page.getByRole('heading', { name: 'Live Stream (SDK)' })).toBeVisible()
189
+ await expect(page.getByTestId('not-authenticated')).not.toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
190
+ await expect(page.getByTestId('nav-feeds')).toBeVisible()
191
+ await expect(page.getByTestId('nav-logout')).toBeVisible()
192
+ })
311
193
 
312
- // Verify mode badge shows LIVE
313
- await expect(page.locator('.mode-live')).toContainText('LIVE')
194
+ test('can view feeds list after login', async ({ page }) => {
195
+ skipIfNoProxy()
196
+ skipIfNoCredentials()
314
197
 
315
- console.log('Live modal opened successfully with video element')
198
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
199
+ await expect(page.getByTestId('nav-feeds')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
316
200
 
317
- // Close modal
318
- await page.locator('.close-button').click()
319
- await expect(page.getByTestId('preview-modal')).not.toBeVisible()
320
- console.log('Modal closed successfully')
321
- } else {
322
- console.log('No main feeds with Live button available')
323
- }
324
- } else {
325
- console.log('No feeds available to test live modal')
326
- }
327
- } else {
328
- console.log('No cameras to test with')
329
- }
330
- })
201
+ await page.click('[data-testid="nav-feeds"]')
202
+ await page.waitForURL('/feeds')
331
203
 
332
- test('modal closes with escape key', async ({ page }) => {
333
- // Navigate to feeds page (auth already injected by beforeEach)
334
- await page.goto('/feeds')
204
+ await expect(page.getByRole('heading', { name: 'Camera Feeds' })).toBeVisible()
335
205
 
336
206
  // Wait for cameras to load
337
207
  await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
338
- timeout: 30000
208
+ timeout: TIMEOUTS.FEEDS_LOAD
339
209
  })
210
+ })
340
211
 
341
- const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
342
-
343
- if (hasCameras) {
344
- // Wait for feeds to load
345
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
346
- timeout: 30000
347
- })
348
-
349
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
350
-
351
- if (hasFeeds) {
352
- // Try View button first, then Live button
353
- const viewButton = page.getByTestId('view-preview-button').first()
354
- const liveButton = page.getByTestId('view-live-button').first()
355
-
356
- const hasViewButton = await viewButton.isVisible().catch(() => false)
357
- const hasLiveButton = await liveButton.isVisible().catch(() => false)
358
-
359
- if (hasViewButton || hasLiveButton) {
360
- const buttonToClick = hasViewButton ? viewButton : liveButton
361
- const buttonType = hasViewButton ? 'View' : 'Live'
362
- console.log(`Opening modal with ${buttonType} button`)
363
-
364
- // Click the button to open modal
365
- await buttonToClick.click()
212
+ test('feeds page shows camera selector when cameras available', async ({ page }) => {
213
+ skipIfNoProxy()
214
+ skipIfNoCredentials()
366
215
 
367
- // Wait for modal to appear
368
- await expect(page.getByTestId('preview-modal')).toBeVisible({ timeout: 10000 })
216
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
217
+ await expect(page.getByTestId('nav-feeds')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
369
218
 
370
- // Press Escape key
371
- await page.keyboard.press('Escape')
219
+ await page.click('[data-testid="nav-feeds"]')
220
+ await page.waitForURL('/feeds')
372
221
 
373
- // Verify modal is closed
374
- await expect(page.getByTestId('preview-modal')).not.toBeVisible({ timeout: 5000 })
375
- console.log('Modal closed with Escape key successfully')
376
- } else {
377
- console.log('No preview or live feeds available to test modal escape')
378
- }
379
- } else {
380
- console.log('No feeds available')
381
- }
382
- } else {
383
- console.log('No cameras to test with')
384
- }
385
- })
386
-
387
- test('modal closes when clicking backdrop', async ({ page }) => {
388
- // Navigate to feeds page (auth already injected by beforeEach)
389
- await page.goto('/feeds')
390
-
391
- // Wait for cameras to load
392
222
  await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
393
- timeout: 30000
223
+ timeout: TIMEOUTS.FEEDS_LOAD
394
224
  })
395
225
 
396
226
  const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
397
-
398
227
  if (hasCameras) {
399
- // Wait for feeds to load
400
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
401
- timeout: 30000
402
- })
403
-
404
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
405
-
406
- if (hasFeeds) {
407
- // Try View button first, then Live button
408
- const viewButton = page.getByTestId('view-preview-button').first()
409
- const liveButton = page.getByTestId('view-live-button').first()
410
-
411
- const hasViewButton = await viewButton.isVisible().catch(() => false)
412
- const hasLiveButton = await liveButton.isVisible().catch(() => false)
413
-
414
- if (hasViewButton || hasLiveButton) {
415
- const buttonToClick = hasViewButton ? viewButton : liveButton
416
- const buttonType = hasViewButton ? 'View' : 'Live'
417
- console.log(`Opening modal with ${buttonType} button`)
418
-
419
- // Click the button to open modal
420
- await buttonToClick.click()
421
-
422
- // Wait for modal to appear
423
- await expect(page.getByTestId('preview-modal')).toBeVisible({ timeout: 10000 })
424
-
425
- // Click on the backdrop (modal-overlay) outside the modal-content
426
- // The modal-overlay covers the entire screen, click in a corner
427
- await page.getByTestId('preview-modal').click({ position: { x: 10, y: 10 } })
428
-
429
- // Verify modal is closed
430
- await expect(page.getByTestId('preview-modal')).not.toBeVisible({ timeout: 5000 })
431
- console.log('Modal closed by clicking backdrop successfully')
432
- } else {
433
- console.log('No preview or live feeds available to test backdrop close')
434
- }
435
- } else {
436
- console.log('No feeds available')
437
- }
228
+ await expect(page.getByTestId('refresh-button')).toBeVisible()
229
+ console.log('Camera selector and refresh button visible')
438
230
  } else {
439
- console.log('No cameras to test with')
231
+ console.log('No cameras in account - skipping camera-specific checks')
440
232
  }
441
233
  })
442
234
 
443
- test('modal displays correct feed information', async ({ page }) => {
444
- // Navigate to feeds page (auth already injected by beforeEach)
445
- await page.goto('/feeds')
235
+ test('can logout after login', async ({ page }) => {
236
+ skipIfNoProxy()
237
+ skipIfNoCredentials()
446
238
 
447
- // Wait for cameras to load
448
- await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
449
- timeout: 30000
450
- })
451
-
452
- const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
453
-
454
- if (hasCameras) {
455
- // Wait for feeds to load
456
- await page.waitForSelector('[data-testid="feeds-table"], .no-feeds', {
457
- timeout: 30000
458
- })
459
-
460
- const hasFeeds = await page.getByTestId('feeds-table').isVisible().catch(() => false)
461
-
462
- if (hasFeeds) {
463
- // Try View button first, then Live button
464
- const viewButton = page.getByTestId('view-preview-button').first()
465
- const liveButton = page.getByTestId('view-live-button').first()
466
-
467
- const hasViewButton = await viewButton.isVisible().catch(() => false)
468
- const hasLiveButton = await liveButton.isVisible().catch(() => false)
469
-
470
- if (hasViewButton || hasLiveButton) {
471
- const buttonToClick = hasViewButton ? viewButton : liveButton
472
- console.log(`Opening modal to verify feed info`)
239
+ await performLogin(page, TEST_USER!, TEST_PASSWORD!)
240
+ await expect(page.getByTestId('nav-logout')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
473
241
 
474
- // Click the button to open modal
475
- await buttonToClick.click()
242
+ await page.click('[data-testid="nav-logout"]')
476
243
 
477
- // Wait for modal to appear
478
- await expect(page.getByTestId('preview-modal')).toBeVisible({ timeout: 10000 })
479
-
480
- // Verify feed info is displayed in modal
481
- const modalFeedInfo = page.locator('.feed-info')
482
- await expect(modalFeedInfo).toBeVisible()
483
-
484
- // Check that Feed ID is displayed
485
- await expect(modalFeedInfo).toContainText('Feed ID:')
486
-
487
- // Check that Type is displayed
488
- await expect(modalFeedInfo).toContainText('Type:')
489
-
490
- // Check that Device is displayed
491
- await expect(modalFeedInfo).toContainText('Device:')
492
-
493
- // Check that Mode is displayed
494
- await expect(modalFeedInfo).toContainText('Mode:')
495
-
496
- console.log('Modal displays correct feed information')
497
-
498
- // Close modal
499
- await page.locator('.close-button').click()
500
- } else {
501
- console.log('No preview or live feeds available to test modal info')
502
- }
503
- } else {
504
- console.log('No feeds available')
505
- }
506
- } else {
507
- console.log('No cameras to test with')
508
- }
244
+ await page.waitForURL('**/')
245
+ await expect(page.getByTestId('not-authenticated')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
246
+ await expect(page.getByTestId('nav-login')).toBeVisible()
509
247
  })
510
248
  })