diff --git a/frontend/jest.config.mjs b/frontend/jest.config.mjs index f3931929c..ab1208af3 100644 --- a/frontend/jest.config.mjs +++ b/frontend/jest.config.mjs @@ -31,5 +31,5 @@ export default { transformIgnorePatterns: [ '/node_modules/(?!syncod)', ], - setupFiles: ['/tests/jest.setup.ts'], + setupFiles: ['/tests/unit/jest.setup.ts'], }; diff --git a/frontend/package.json b/frontend/package.json index d7f9c3386..09ff850b5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ "@eslint/js": "^9.26.0", "@medv/finder": "^4.0.2", "@neodrag/react": "^2.3.0", + "@playwright/test": "^1.52.0", "@sentry/browser": "^9.18.0", "@svg-maps/world": "^1.0.1", "@tanstack/react-query": "^5.76.0", @@ -64,6 +65,7 @@ "mobx": "^6.13.7", "mobx-persist-store": "^1.1.8", "mobx-react-lite": "^4.1.0", + "playwright": "^1.52.0", "prismjs": "^1.30.0", "rc-time-picker": "^3.7.3", "react": "^19.1.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 000000000..b22d3feef --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,37 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import 'dotenv/config'; +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests/playwright', + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: process.env.CI ? 'html' : 'list', + use: { + baseURL: 'http://localhost:3333', + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + dependencies: ['setup'], + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn start', + url: 'http://localhost:3333', + timeout: 120 * 1000, + }, +}); \ No newline at end of file diff --git a/frontend/tests/playwright/auth.setup.ts b/frontend/tests/playwright/auth.setup.ts new file mode 100644 index 000000000..f3be63728 --- /dev/null +++ b/frontend/tests/playwright/auth.setup.ts @@ -0,0 +1,26 @@ +import { authStateFile, testUseAuthState } from './helpers'; +import { test } from '@playwright/test'; + +testUseAuthState(); + +test('authenticate', async ({ page }) => { + await page.goto('/'); + + try { + const url = page.url(); + + if (url.includes('login')) { + await page.locator('[data-test-id="login"]').click(); + await page.locator('.ant-input-affix-wrapper').first().click(); + await page.locator('[data-test-id="login"]').fill('andrei@openreplay.com'); + await page.locator('[data-test-id="password"]').click(); + await page.locator('[data-test-id="password"]').fill('Andrey123!'); + await page.locator('[data-test-id="log-button"]').click(); + } + await page.waitForSelector('h1:has-text("Sessions")', { timeout: 10000 }); + } catch (e) {} + + try { + await page.context().storageState({ path: authStateFile }); + } catch {} +}); \ No newline at end of file diff --git a/frontend/tests/playwright/helpers.ts b/frontend/tests/playwright/helpers.ts new file mode 100644 index 000000000..9a05ef0c5 --- /dev/null +++ b/frontend/tests/playwright/helpers.ts @@ -0,0 +1,18 @@ +import { mkdirSync } from "fs"; +import { exists } from "i18next"; +import { dirname } from "path"; +import { test } from "@playwright/test"; + +export const authStateFile = 'node_modules/playwright/auth-state.json'; + +mkdirSync(dirname(authStateFile), { recursive: true }); + +export function testUseAuthState() { + if (exists(authStateFile)) { + test.use({ storageState: authStateFile }); + } + + test.afterEach(async ({ page }) => { + await page.context().storageState({ path: authStateFile }); + }) +} \ No newline at end of file diff --git a/frontend/tests/playwright/session-list.spec.ts b/frontend/tests/playwright/session-list.spec.ts new file mode 100644 index 000000000..d006a085a --- /dev/null +++ b/frontend/tests/playwright/session-list.spec.ts @@ -0,0 +1,13 @@ +import { test, expect } from '@playwright/test'; +import { testUseAuthState } from './helpers'; + +testUseAuthState(); + +test('test', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: 'OpenReplay Documentation Site' }).click(); + await page.getByRole('menuitem', { name: 'Android' }).locator('div').click(); + await page.getByRole('button', { name: 'Android caret-down' }).click(); + await page.getByText('OpenReplay Documentation Site').click(); + await page.locator('#session-item').first().click(); +}); \ No newline at end of file diff --git a/frontend/tests/playwright/sign-in.spec.ts b/frontend/tests/playwright/sign-in.spec.ts new file mode 100644 index 000000000..302a9d18f --- /dev/null +++ b/frontend/tests/playwright/sign-in.spec.ts @@ -0,0 +1,12 @@ +import { test, expect } from '@playwright/test'; + +test('Sign in flow', async ({ page }) => { + await page.goto('/'); + await page.locator('[data-test-id="login"]').click(); + await page.locator('.ant-input-affix-wrapper').first().click(); + await page.locator('[data-test-id="login"]').fill('andrei@openreplay.com'); + await page.locator('[data-test-id="password"]').click(); + await page.locator('[data-test-id="password"]').fill('Andrey123!'); + await page.locator('[data-test-id="log-button"]').click(); + await expect(page.getByRole('heading', { name: 'Sessions' })).toBeVisible(); +}); \ No newline at end of file diff --git a/frontend/tests/ListWalker.test.ts b/frontend/tests/unit/ListWalker.test.ts similarity index 86% rename from frontend/tests/ListWalker.test.ts rename to frontend/tests/unit/ListWalker.test.ts index 5fce51128..aaac7c592 100644 --- a/frontend/tests/ListWalker.test.ts +++ b/frontend/tests/unit/ListWalker.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect, beforeEach, jest } from '@jest/globals'; -import ListWalker from '../app/player/common/ListWalker'; -import type { Timed } from '../app/player/common/types'; +import ListWalker from '../../app/player/common/ListWalker'; +import type { Timed } from '../../app/player/common/types'; interface Item extends Timed { value?: string; @@ -17,24 +17,24 @@ describe('ListWalker', () => { test('append maintains order and prevents out of order inserts', () => { walker.append({ time: 1 }); walker.append({ time: 3 }); - expect(walker.list.map(i => i.time)).toEqual([1, 3]); + expect(walker.list.map((i) => i.time)).toEqual([1, 3]); walker.append({ time: 2 }); - expect(walker.list.map(i => i.time)).toEqual([1, 3]); + expect(walker.list.map((i) => i.time)).toEqual([1, 3]); expect((console.error as jest.Mock).mock.calls.length).toBe(1); }); test('unshift prepends items', () => { walker.append({ time: 2 }); walker.unshift({ time: 1 }); - expect(walker.list.map(i => i.time)).toEqual([1, 2]); + expect(walker.list.map((i) => i.time)).toEqual([1, 2]); }); test('insert places item according to time', () => { walker.append({ time: 1 }); walker.append({ time: 3 }); walker.insert({ time: 2 }); - expect(walker.list.map(i => i.time)).toEqual([1, 2, 3]); + expect(walker.list.map((i) => i.time)).toEqual([1, 2, 3]); }); test('moveGetLast advances pointer and returns item', () => { @@ -80,4 +80,4 @@ describe('ListWalker', () => { expect(collected).toEqual([1, 2, 1]); expect(walker.countNow).toBe(1); }); -}); \ No newline at end of file +}); diff --git a/frontend/tests/MFileReader.test.ts b/frontend/tests/unit/MFileReader.test.ts similarity index 90% rename from frontend/tests/MFileReader.test.ts rename to frontend/tests/unit/MFileReader.test.ts index 6fd931a56..3e67e1348 100644 --- a/frontend/tests/MFileReader.test.ts +++ b/frontend/tests/unit/MFileReader.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from '@jest/globals'; -import MFileReader from '../app/player/web/messages/MFileReader'; -import { MType } from '../app/player/web/messages/raw.gen'; +import MFileReader from '../../app/player/web/messages/MFileReader'; +import { MType } from '../../app/player/web/messages/raw.gen'; function encodeUint(value: number): Uint8Array { const bytes: number[] = []; diff --git a/frontend/tests/MessageLoader.test.ts b/frontend/tests/unit/MessageLoader.test.ts similarity index 92% rename from frontend/tests/MessageLoader.test.ts rename to frontend/tests/unit/MessageLoader.test.ts index 7272ef501..9eaffccf4 100644 --- a/frontend/tests/MessageLoader.test.ts +++ b/frontend/tests/unit/MessageLoader.test.ts @@ -1,13 +1,13 @@ import { describe, expect, test, jest, beforeEach } from '@jest/globals'; -import MessageLoader from '../app/player/web/MessageLoader'; -import { MType } from '../app/player/web/messages'; +import MessageLoader from '../../app/player/web/MessageLoader'; +import { MType } from '../../app/player/web/messages'; import fs from 'fs'; import path from 'path'; import { TextDecoder } from 'util'; const loadFilesMock = jest.fn(async () => {}); -jest.mock('../app/player/web/network/loadFiles', () => ({ +jest.mock('../../app/player/web/network/loadFiles', () => ({ __esModule: true, loadFiles: jest.fn(async () => {}), requestTarball: jest.fn(), @@ -17,7 +17,7 @@ jest.mock('../app/player/web/network/loadFiles', () => ({ const decryptSessionBytesMock = jest.fn((b: Uint8Array) => Promise.resolve(b)); -jest.mock('../app/player/web/network/crypto', () => ({ +jest.mock('../../app/player/web/network/crypto', () => ({ __esModule: true, decryptSessionBytes: jest.fn((b: Uint8Array) => Promise.resolve(b)), })); @@ -32,11 +32,11 @@ jest.mock('Player/common/tarball', () => ({ default: jest.fn((b: Uint8Array) => b), })); -import MFileReader from '../app/player/web/messages/MFileReader'; +import MFileReader from '../../app/player/web/messages/MFileReader'; const readNextMock = jest.fn(); -jest.mock('../app/player/web/messages/MFileReader', () => { +jest.mock('../../app/player/web/messages/MFileReader', () => { return { __esModule: true, default: jest.fn().mockImplementation(() => { @@ -49,7 +49,7 @@ jest.mock('../app/player/web/messages/MFileReader', () => { }; }); -import { mockSession } from './mocks/sessionData'; +import { mockSession } from '../mocks/sessionData'; const createStore = () => { const state: Record = {}; diff --git a/frontend/tests/TabManager.test.ts b/frontend/tests/unit/TabManager.test.ts similarity index 77% rename from frontend/tests/TabManager.test.ts rename to frontend/tests/unit/TabManager.test.ts index 02da05f4f..09bea22b5 100644 --- a/frontend/tests/TabManager.test.ts +++ b/frontend/tests/unit/TabManager.test.ts @@ -1,20 +1,24 @@ import { it, expect, beforeEach, jest } from '@jest/globals'; -import TabSessionManager from '../app/player/web/TabManager'; -import SimpleStore from '../app/player/common/SimpleStore'; -import { TYPES as EVENT_TYPES } from '../app/types/session/event'; -import { MType } from '../app/player/web/messages/raw.gen'; +import TabSessionManager from '../../app/player/web/TabManager'; +import SimpleStore from '../../app/player/common/SimpleStore'; +import { TYPES as EVENT_TYPES } from '../../app/types/session/event'; +import { MType } from '../../app/player/web/messages/raw.gen'; -jest.mock('@medv/finder', () => ({ default: jest.fn(() => 'mocked network-proxy content') })); +jest.mock('@medv/finder', () => ({ + default: jest.fn(() => 'mocked network-proxy content'), +})); jest.mock('syncod', () => { return { - Decoder: jest.fn().mockImplementation(() => ({ decode: jest.fn(), set: jest.fn() })), + Decoder: jest + .fn() + .mockImplementation(() => ({ decode: jest.fn(), set: jest.fn() })), }; }); jest.mock('js-untar', () => ({ - __esModule: true, - default: jest.fn(), - })); + __esModule: true, + default: jest.fn(), +})); class FakeScreen { displayFrame = jest.fn(); @@ -35,7 +39,14 @@ beforeEach(() => { tabNames: {}, eventCount: 0, }); - manager = new TabSessionManager(session, store as any, new FakeScreen() as any, 'tab1', setSize, 0); + manager = new TabSessionManager( + session, + store as any, + new FakeScreen() as any, + 'tab1', + setSize, + 0, + ); jest.runOnlyPendingTimers(); jest.useRealTimers(); }); @@ -65,14 +76,21 @@ it('resetMessageManagers should clear managers', () => { }); it('onFileReadSuccess should update store with lists and performance data', () => { - (manager as any).performanceTrackManager['chart'] = [{ time: 1, usedHeap: 0, totalHeap: 0, fps: null, cpu: null, nodesCount: 0 }]; + (manager as any).performanceTrackManager['chart'] = [ + { time: 1, usedHeap: 0, totalHeap: 0, fps: null, cpu: null, nodesCount: 0 }, + ]; (manager as any).performanceTrackManager['cpuAvailable'] = true; (manager as any).performanceTrackManager['fpsAvailable'] = true; manager.locationManager.append({ time: 2, url: 'http://example.com' } as any); manager.onFileReadSuccess(); const state = store.get().tabStates['tab1']; expect(state.performanceChartData.length).toBe(1); - expect(state.performanceAvailability).toEqual({ cpu: true, fps: true, heap: false, nodes: true }); + expect(state.performanceAvailability).toEqual({ + cpu: true, + fps: true, + heap: false, + nodes: true, + }); expect(state.urlsList[0].url).toBe('http://example.com'); }); @@ -98,4 +116,4 @@ it('sortDomRemoveMessages comparator should prioritize head nodes', () => { expect(comparator(msgs[2], msgs[0])).toBe(1); expect(comparator(msgs[0], msgs[1])).toBe(-1); expect(comparator(msgs[1], msgs[0])).toBe(1); -}); \ No newline at end of file +}); diff --git a/frontend/tests/create.test.ts b/frontend/tests/unit/create.test.ts similarity index 94% rename from frontend/tests/create.test.ts rename to frontend/tests/unit/create.test.ts index 080ffac5d..a644347e6 100644 --- a/frontend/tests/create.test.ts +++ b/frontend/tests/unit/create.test.ts @@ -33,17 +33,17 @@ class MockWebLivePlayer { ) {} } -jest.mock('../app/player/mobile/IOSPlayer', () => ({ +jest.mock('../../app/player/mobile/IOSPlayer', () => ({ __esModule: true, default: MockIOSPlayer, })); -jest.mock('../app/player/web/WebPlayer', () => ({ +jest.mock('../../app/player/web/WebPlayer', () => ({ __esModule: true, default: MockWebPlayer, })); -jest.mock('../app/player/web/WebLivePlayer', () => ({ +jest.mock('../../app/player/web/WebLivePlayer', () => ({ __esModule: true, default: MockWebLivePlayer, })); @@ -54,7 +54,7 @@ import { createClickMapPlayer, createLiveWebPlayer, createClipPlayer, -} from '../app/player/create'; +} from '../../app/player/create'; const session = { id: 1 } as any; const errorHandler = { error: jest.fn() }; diff --git a/frontend/tests/featureFlag.type.test.js b/frontend/tests/unit/featureFlag.type.test.js similarity index 81% rename from frontend/tests/featureFlag.type.test.js rename to frontend/tests/unit/featureFlag.type.test.js index b427eea93..0d3ba69e4 100644 --- a/frontend/tests/featureFlag.type.test.js +++ b/frontend/tests/unit/featureFlag.type.test.js @@ -1,28 +1,30 @@ -import FeatureFlag, { Conditions, Variant } from '../app/mstore/types/FeatureFlag'; +import FeatureFlag, { + Conditions, + Variant, +} from '../../app/mstore/types/FeatureFlag'; import { jest, test, expect, describe } from '@jest/globals'; - jest.mock('App/mstore/types/filter', () => { - let filterData = { filters: [] } + let filterData = { filters: [] }; class MockFilter { - ID_KEY = "filterId" - filterId = '' - name = '' - filters = [] - eventsOrder = 'then' - eventsOrderSupport = ['then', 'or', 'and'] - startTimestamp = 0 - endTimestamp = 0 + ID_KEY = 'filterId'; + filterId = ''; + name = ''; + filters = []; + eventsOrder = 'then'; + eventsOrderSupport = ['then', 'or', 'and']; + startTimestamp = 0; + endTimestamp = 0; fromJson(json) { - this.name = json.name - this.filters = json.filters.map((i) => i) - this.eventsOrder = json.eventsOrder - return this + this.name = json.name; + this.filters = json.filters.map((i) => i); + this.eventsOrder = json.eventsOrder; + return this; } } - return MockFilter -}) + return MockFilter; +}); describe('Feature flag type test', () => { // Test cases for Conditions class diff --git a/frontend/tests/featureFlagsStore.test.js b/frontend/tests/unit/featureFlagsStore.test.js similarity index 100% rename from frontend/tests/featureFlagsStore.test.js rename to frontend/tests/unit/featureFlagsStore.test.js diff --git a/frontend/tests/jest.setup.ts b/frontend/tests/unit/jest.setup.ts similarity index 100% rename from frontend/tests/jest.setup.ts rename to frontend/tests/unit/jest.setup.ts diff --git a/frontend/tests/searchStore.test.ts b/frontend/tests/unit/searchStore.test.ts similarity index 70% rename from frontend/tests/searchStore.test.ts rename to frontend/tests/unit/searchStore.test.ts index 7ede4fd68..0872bff5f 100644 --- a/frontend/tests/searchStore.test.ts +++ b/frontend/tests/unit/searchStore.test.ts @@ -37,9 +37,27 @@ jest.mock('Types/filter/newFilter', () => { const { FilterKey, FilterCategory } = require('Types/filter/filterType'); return { filtersMap: { - [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterKey.USERID, category: FilterCategory.USER, operator: 'is', value: [''] }, - [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterKey.DURATION, category: FilterCategory.SESSION, operator: 'is', value: [0, 0] }, - [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterKey.ISSUE, category: FilterCategory.ISSUE, operator: 'is', value: [] }, + [FilterKey.USERID]: { + key: FilterKey.USERID, + type: FilterKey.USERID, + category: FilterCategory.USER, + operator: 'is', + value: [''], + }, + [FilterKey.DURATION]: { + key: FilterKey.DURATION, + type: FilterKey.DURATION, + category: FilterCategory.SESSION, + operator: 'is', + value: [0, 0], + }, + [FilterKey.ISSUE]: { + key: FilterKey.ISSUE, + type: FilterKey.ISSUE, + category: FilterCategory.ISSUE, + operator: 'is', + value: [], + }, }, conditionalFiltersMap: {}, generateFilterOptions: jest.fn(() => []), @@ -51,27 +69,31 @@ jest.mock('Types/filter/newFilter', () => { const mockSessionFetch = jest.fn().mockResolvedValue({}); const mockSessionStore = { - fetchSessions: mockSessionFetch, - total: 0, - clearList: jest.fn(), - }; - const mockSettingsStore = { - sessionSettings: { durationFilter: { count: 0 } }, - }; + fetchSessions: mockSessionFetch, + total: 0, + clearList: jest.fn(), +}; +const mockSettingsStore = { + sessionSettings: { durationFilter: { count: 0 } }, +}; jest.mock('App/services', () => ({ searchService: { fetchSavedSearch: jest.fn() }, - sessionService: { getSessions: jest.fn().mockResolvedValue({ sessions: [], total: 0 }) }, + sessionService: { + getSessions: jest.fn().mockResolvedValue({ sessions: [], total: 0 }), + }, })); jest.mock('App/mstore', () => ({ sessionStore: mockSessionStore, settingsStore: mockSettingsStore, })); -import SearchStore, { checkValues, filterMap } from '../app/mstore/searchStore'; -import SavedSearch from '../app/mstore/types/savedSearch'; -import { FilterCategory, FilterKey } from '../app/types/filter/filterType'; - +import SearchStore, { + checkValues, + filterMap, +} from '../../app/mstore/searchStore'; +import SavedSearch from '../../app/mstore/types/savedSearch'; +import { FilterCategory, FilterKey } from '../../app/types/filter/filterType'; describe('searchStore utilities', () => { it('checkValues handles duration', () => { @@ -113,7 +135,9 @@ describe('SearchStore class', () => { it('applySavedSearch sets filters', () => { const saved = new SavedSearch({ name: 'test', - filter: { filters: [{ key: FilterKey.USERID, value: ['123'], operator: 'is' }] }, + filter: { + filters: [{ key: FilterKey.USERID, value: ['123'], operator: 'is' }], + }, }); store.applySavedSearch(saved); expect(store.savedSearch).toBe(saved); @@ -129,11 +153,17 @@ describe('SearchStore class', () => { }); it('fetchSessions applies duration filter from settings', async () => { - mockSettingsStore.sessionSettings.durationFilter = { operator: '<', count: 1, countType: 'sec' }; + mockSettingsStore.sessionSettings.durationFilter = { + operator: '<', + count: 1, + countType: 'sec', + }; await store.fetchSessions(); const call = mockSessionFetch.mock.calls[0][0]; - const duration = call.filters.find((f: any) => f.type === FilterKey.DURATION); + const duration = call.filters.find( + (f: any) => f.type === FilterKey.DURATION, + ); expect(duration).toBeTruthy(); expect(duration.value).toEqual([1000, 0]); }); -}); \ No newline at end of file +}); diff --git a/frontend/tests/session.test.js b/frontend/tests/unit/session.test.js similarity index 79% rename from frontend/tests/session.test.js rename to frontend/tests/unit/session.test.js index 923bca55f..ca5087171 100644 --- a/frontend/tests/session.test.js +++ b/frontend/tests/unit/session.test.js @@ -1,10 +1,10 @@ import { describe, expect, test } from '@jest/globals'; -import Session from '../app/types/session'; -import { Click, Location } from '../app/types/session/event'; -import Issue from '../app/types/session/issue'; -import { session } from './mocks/sessionResponse'; -import { issues, events } from "./mocks/sessionData"; +import Session from '../../app/types/session'; +import { Click, Location } from '../../app/types/session/event'; +import Issue from '../../app/types/session/issue'; +import { session } from '../mocks/sessionResponse'; +import { issues, events } from '../mocks/sessionData'; describe('Testing Session class', () => { const sessionInfo = new Session(session.data); @@ -27,6 +27,6 @@ describe('Testing Session class', () => { expect([...sessionInfo.issues]).toMatchObject(issues); }); test('checking events mapping', () => { - expect([...sessionInfo.events.slice(0, 10)]).toMatchObject(events) - }) + expect([...sessionInfo.events.slice(0, 10)]).toMatchObject(events); + }); }); diff --git a/frontend/tests/sessionStore.test.ts b/frontend/tests/unit/sessionStore.test.ts similarity index 95% rename from frontend/tests/sessionStore.test.ts rename to frontend/tests/unit/sessionStore.test.ts index 43ea9be18..700ef7fda 100644 --- a/frontend/tests/sessionStore.test.ts +++ b/frontend/tests/unit/sessionStore.test.ts @@ -1,20 +1,20 @@ -import { sessionService } from '../app/services'; -import Session from '../app/types/session' +import { sessionService } from '../../app/services'; +import Session from '../../app/types/session'; import { beforeEach, describe, expect, it, jest } from '@jest/globals'; -import SessionStore from '../app/mstore/sessionStore'; -import { searchStore } from '../app/mstore/index'; -import { checkEventWithFilters } from '../app/components/Session_/Player/Controls/checkEventWithFilters'; -import { mockSession } from './mocks/sessionData'; +import SessionStore from '../../app/mstore/sessionStore'; +import { searchStore } from '../../app/mstore/index'; +import { checkEventWithFilters } from '../../app/components/Session_/Player/Controls/checkEventWithFilters'; +import { mockSession } from '../mocks/sessionData'; -jest.mock('../app/player', () => ({ +jest.mock('../../app/player', () => ({ createWebPlayer: jest.fn(), createIOSPlayer: jest.fn(), createClickMapPlayer: jest.fn(), createLiveWebPlayer: jest.fn(), - createClipPlayer: jest.fn() + createClipPlayer: jest.fn(), })); -jest.mock('../app/services', () => ({ +jest.mock('../../app/services', () => ({ sessionService: { getSessions: jest.fn(), getLiveSessions: jest.fn(), @@ -41,7 +41,7 @@ jest.mock( }), ); -jest.mock('../app/mstore/index', () => ({ +jest.mock('../../app/mstore/index', () => ({ searchStore: { instance: { filters: [], diff --git a/frontend/tests/spotPlayerStore.test.ts b/frontend/tests/unit/spotPlayerStore.test.ts similarity index 98% rename from frontend/tests/spotPlayerStore.test.ts rename to frontend/tests/unit/spotPlayerStore.test.ts index 043b4eff6..5fcb68543 100644 --- a/frontend/tests/spotPlayerStore.test.ts +++ b/frontend/tests/unit/spotPlayerStore.test.ts @@ -2,7 +2,7 @@ import { jest, beforeEach, describe, expect, it } from '@jest/globals'; import spotPlayerStore, { PANELS, -} from '../app/components/Spots/SpotPlayer/spotPlayerStore'; +} from '../../app/components/Spots/SpotPlayer/spotPlayerStore'; jest.mock('App/player', () => ({ getResourceFromNetworkRequest: jest.fn(), diff --git a/frontend/tests/types.resource.test.ts b/frontend/tests/unit/types.resource.test.ts similarity index 98% rename from frontend/tests/types.resource.test.ts rename to frontend/tests/unit/types.resource.test.ts index 8fba7ab38..d7c4745b5 100644 --- a/frontend/tests/types.resource.test.ts +++ b/frontend/tests/unit/types.resource.test.ts @@ -6,11 +6,11 @@ import { Resource, getResourceFromResourceTiming, getResourceFromNetworkRequest, -} from '../app/player/web/types/resource'; +} from '../../app/player/web/types/resource'; import type { ResourceTiming, NetworkRequest, -} from '../app/player/web/messages'; +} from '../../app/player/web/messages'; import { test, describe, expect } from '@jest/globals'; describe('getURLExtention', () => { diff --git a/frontend/tests/urlResolve.test.ts b/frontend/tests/unit/urlResolve.test.ts similarity index 51% rename from frontend/tests/urlResolve.test.ts rename to frontend/tests/unit/urlResolve.test.ts index 12c89f207..08cac7364 100644 --- a/frontend/tests/urlResolve.test.ts +++ b/frontend/tests/unit/urlResolve.test.ts @@ -1,10 +1,10 @@ -import { test, describe, expect } from "@jest/globals"; -import { resolveCSS } from '../app/player/web/messages/rewriter/urlResolve'; +import { test, describe, expect } from '@jest/globals'; +import { resolveCSS } from '../../app/player/web/messages/rewriter/urlResolve'; const strings = [ `@import "custom.css";`, `@import url("chrome://communicator/skin/");`, - `@import '../app/custom.css';`, + `@import '../../app/custom.css';`, `@import "styles/common.css";`, `@import "/css/commonheader.css";`, `@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;700;900&display=swap');`, @@ -13,26 +13,26 @@ const strings = [ #login-required { color: #fff; };`, - `@import url("style.css") screen and (max-width: 600px);` + `@import url("style.css") screen and (max-width: 600px);`, ]; const testStrings = [ -`@import url("https://example.com/custom.css");`, -`@import url("chrome://communicator/skin/");`, -`@import url('https://example.com/app/custom.css');`, -`@import url("https://example.com/styles/common.css");`, -`@import url("https://example.com/css/commonheader.css");`, -`@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;700;900&display=swap');`, -`@import url('https://example.com/css/onboardcustom.css'); + `@import url("https://example.com/custom.css");`, + `@import url("chrome://communicator/skin/");`, + `@import url('https://example.com/app/custom.css');`, + `@import url("https://example.com/styles/common.css");`, + `@import url("https://example.com/css/commonheader.css");`, + `@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;700;900&display=swap');`, + `@import url('https://example.com/css/onboardcustom.css'); @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;700;900&display=swap'); #login-required { color: #fff; };`, -`@import url("https://example.com/style.css") screen and (max-width: 600px);` -] + `@import url("https://example.com/style.css") screen and (max-width: 600px);`, +]; describe('resolveCSS', () => { test('should rewrite the CSS with the correct URLs', () => { strings.forEach((string, i) => { expect(resolveCSS('https://example.com', string)).toBe(testStrings[i]); - }) - }) -}) \ No newline at end of file + }); + }); +}); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 52532d96f..e9ea28801 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -39,6 +39,6 @@ "app/**/*.jsx", "window.d.ts", "cypress/snapshots/sessionStore.test.ts", - "tests/create.test.ts" + "tests/unit/create.test.ts" ] } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 37acfb81f..41516929c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2889,6 +2889,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.52.0": + version: 1.52.0 + resolution: "@playwright/test@npm:1.52.0" + dependencies: + playwright: "npm:1.52.0" + bin: + playwright: cli.js + checksum: 10c1/b7a57ce045e246d927cc8cd2091864313be6e778cc6e9f2484e6273ce091fd6eb8a68c177cf6cbd484dda68a4fa85dfaa8109ae2618475c0f07f6447b3988379 + languageName: node + linkType: hard + "@rc-component/async-validator@npm:^5.0.3": version: 5.0.4 resolution: "@rc-component/async-validator@npm:5.0.4" @@ -8669,6 +8680,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10c1/39f892d6e26b3d01f7e18fac9dd334d2c2a250ac2d534066e033dc5081c669b6c8b9e61f8ceb396fd9aafc210a2a3f56d8c9064768db1c1d2f34ad985c6d5b02 + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -8679,6 +8700,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -12885,6 +12915,7 @@ __metadata: "@medv/finder": "npm:^4.0.2" "@neodrag/react": "npm:^2.3.0" "@openreplay/sourcemap-uploader": "npm:^3.0.10" + "@playwright/test": "npm:^1.52.0" "@sentry/browser": "npm:^9.18.0" "@svg-maps/world": "npm:^1.0.1" "@tanstack/react-query": "npm:^5.76.0" @@ -12957,6 +12988,7 @@ __metadata: mobx-persist-store: "npm:^1.1.8" mobx-react-lite: "npm:^4.1.0" node-gyp: "npm:^9.0.0" + playwright: "npm:^1.52.0" postcss: "npm:^8.5.3" postcss-import: "npm:^16.1.0" postcss-loader: "npm:^8.1.1" @@ -13416,6 +13448,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.52.0": + version: 1.52.0 + resolution: "playwright-core@npm:1.52.0" + bin: + playwright-core: cli.js + checksum: 10c1/86d3a13d6a5e426757bfd620f87f52696cec42f1e6e39e40d595b8cbab602447d1e309ad67f51e03ced147677de7824518abb9587254fb14c8b4252b7b1bae2a + languageName: node + linkType: hard + +"playwright@npm:1.52.0, playwright@npm:^1.52.0": + version: 1.52.0 + resolution: "playwright@npm:1.52.0" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.52.0" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10c1/58f5fa976f733fd64fba716f2e605cfd1fcdffc9f874f4baa657330ff9c4e9f8b1d49c02d33fbcea48c40472cc5ed07dec8d0291dac38cc2ed579449c0053ee5 + languageName: node + linkType: hard + "plist@npm:^3.0.1": version: 3.1.0 resolution: "plist@npm:3.1.0"