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.
- package/CHANGELOG.md +45 -8
- package/README.md +38 -0
- package/docs/AI-CONTEXT.md +1 -1
- package/examples/vue-bridges/README.md +126 -0
- package/examples/vue-bridges/bridges-screenshot.png +0 -0
- package/examples/vue-bridges/package-lock.json +130 -42
- package/examples/vue-bridges/package.json +2 -2
- package/examples/vue-cameras/README.md +142 -0
- package/examples/vue-cameras/cameras-screenshot.png +0 -0
- package/examples/vue-cameras/package-lock.json +130 -42
- package/examples/vue-cameras/package.json +2 -2
- package/examples/vue-feeds/README.md +162 -0
- package/examples/vue-feeds/e2e/auth.spec.ts +182 -444
- package/examples/vue-feeds/feeds-screenshot.png +0 -0
- package/examples/vue-feeds/package-lock.json +130 -42
- package/examples/vue-feeds/package.json +2 -2
- package/examples/vue-feeds/playwright.config.ts +28 -7
- package/examples/vue-media/README.md +187 -0
- package/examples/vue-media/e2e/auth.spec.ts +218 -298
- package/examples/vue-media/media-screenshot.png +0 -0
- package/examples/vue-media/package-lock.json +130 -42
- package/examples/vue-media/package.json +2 -2
- package/examples/vue-media/playwright.config.ts +28 -7
- package/examples/vue-users/README.md +58 -15
- package/examples/vue-users/package-lock.json +132 -44
- package/examples/vue-users/package.json +3 -3
- package/examples/vue-users/users-screenshot.png +0 -0
- package/package.json +12 -11
|
@@ -1,510 +1,248 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* E2E tests for the Vue Feeds Example - OAuth Login Flow
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
22
|
+
import { test, expect, Page } from '@playwright/test'
|
|
23
|
+
import { baseURL } from '../playwright.config'
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
timeout: 30000
|
|
197
|
-
})
|
|
125
|
+
test.describe('Vue Feeds Example - Auth', () => {
|
|
126
|
+
let proxyAccessible = false
|
|
198
127
|
|
|
199
|
-
|
|
200
|
-
|
|
128
|
+
function skipIfNoProxy() {
|
|
129
|
+
test.skip(!proxyAccessible, 'OAuth proxy not accessible')
|
|
130
|
+
}
|
|
201
131
|
|
|
202
|
-
|
|
203
|
-
|
|
132
|
+
function skipIfNoCredentials() {
|
|
133
|
+
test.skip(!TEST_USER || !TEST_PASSWORD, 'Test credentials not available')
|
|
134
|
+
}
|
|
204
135
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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(
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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('
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
298
|
-
|
|
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
|
-
|
|
301
|
-
|
|
180
|
+
test('complete OAuth login flow and view feeds', async ({ page }) => {
|
|
181
|
+
skipIfNoProxy()
|
|
182
|
+
skipIfNoCredentials()
|
|
302
183
|
|
|
303
|
-
|
|
304
|
-
|
|
184
|
+
await page.goto('/')
|
|
185
|
+
await expect(page.getByTestId('not-authenticated')).toBeVisible()
|
|
305
186
|
|
|
306
|
-
|
|
307
|
-
await expect(page.getByTestId('preview-video')).toBeVisible({ timeout: 10000 })
|
|
187
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
308
188
|
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
313
|
-
|
|
194
|
+
test('can view feeds list after login', async ({ page }) => {
|
|
195
|
+
skipIfNoProxy()
|
|
196
|
+
skipIfNoCredentials()
|
|
314
197
|
|
|
315
|
-
|
|
198
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
199
|
+
await expect(page.getByTestId('nav-feeds')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
316
200
|
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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:
|
|
208
|
+
timeout: TIMEOUTS.FEEDS_LOAD
|
|
339
209
|
})
|
|
210
|
+
})
|
|
340
211
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
368
|
-
|
|
216
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
217
|
+
await expect(page.getByTestId('nav-feeds')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
369
218
|
|
|
370
|
-
|
|
371
|
-
|
|
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:
|
|
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
|
-
|
|
400
|
-
|
|
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
|
|
231
|
+
console.log('No cameras in account - skipping camera-specific checks')
|
|
440
232
|
}
|
|
441
233
|
})
|
|
442
234
|
|
|
443
|
-
test('
|
|
444
|
-
|
|
445
|
-
|
|
235
|
+
test('can logout after login', async ({ page }) => {
|
|
236
|
+
skipIfNoProxy()
|
|
237
|
+
skipIfNoCredentials()
|
|
446
238
|
|
|
447
|
-
|
|
448
|
-
await page.
|
|
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
|
-
|
|
475
|
-
await buttonToClick.click()
|
|
242
|
+
await page.click('[data-testid="nav-logout"]')
|
|
476
243
|
|
|
477
|
-
|
|
478
|
-
|
|
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
|
})
|