Skip to content

Commit

Permalink
WIP: Playground redesign (#144)
Browse files Browse the repository at this point in the history
* chore: move playground to app router

* feat: add hero section

* feat: visitorId box

* chore: move components into playground

* feat: style table v1

* feat: table link, prevent icon wrapping to a separate line

* feat: fix external link icon color and location text

* feat: nicer map

* feat: fix map height, suspect score n/a

* feat: analyze again button

* feat: accordion wip

* feat: improve accordion

* feat: finish how to use this playground

* chore: update Next

* feat: finish how to use this playground

* feat: json section

* ci: fix report status job

* feat: layout fix

* feat: Learn more section

* feat: scrolly scroll

* feat: improve scrollbar position

* feat: finally fix scroll area

* feat: basic copy button

* feat: copied state for copy button

* styles: running intelligence padding

* styles: responsiveness whitespace

* styles: responsiveness font-size

* styles: more responsiveness

* feat: make signal table collapsible

* feat: finalize open-close chevron

* fix tests

* feat: try @microlink/react-json-view, does not work

* try react18-json-viewer, much better

* feat: improve json viewer

* feat: use json viewer

* improve copy function, organize styles

* fix build

* fix map

* ci: fix e2e tests

* styles: update minor styles

* styles: learn more section horizontal scroll on mobile

* styles: update padding

* feat: use accuracyRadius

* fix build

* styles: fix chevron visibility

* chore: simplify accordion implementation

* chore: self review fixes

* chore: fix build

* chore: fix build

* chore: review fixes

* Update src/client/components/common/componentUtils.tsx

Co-authored-by: Przemysław Żydek <[email protected]>

* fix lint

* Feat/final redesign touches (#147)

* feat: external link animation

* feat: custom spinner

* feat: running intelligence animation

* fix: move refresh button to the top

* feat: add remote control smart signal

* feat: add velocity smart signal

* left align hero section on mobile

* fix accordion color

* fix build

* feat: use the same mapbox tiles as the main website

* ci: run check even when PR is not into main

* fix: spinner size

* Playground Layout: Put visitor ID in Identification column (#148)

* feat: external link animation

* feat: custom spinner

* feat: running intelligence animation

* fix: move refresh button to the top

* feat: add remote control smart signal

* feat: add velocity smart signal

* left align hero section on mobile

* fix accordion color

* fix build

* feat: use the same mapbox tiles as the main website

* ci: run check even when PR is not into main

* feat: put visitor ID box in the identification column

* fix mobile layout

* self-review: remove comments

* test: fix locator

* fix map z index

* fix: disable scroll restoration on playground

* feat: add table animations

* fix: remove !important

---------

Co-authored-by: Przemysław Żydek <[email protected]>
  • Loading branch information
JuroUhlar and TheUnderScorer authored Jul 15, 2024
1 parent 74bf00a commit abea4e7
Show file tree
Hide file tree
Showing 73 changed files with 2,470 additions and 1,029 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
env:
# Playwright headless browsers running in CI get low confidence scores, causing flaky e2e tests. Lower the confidence score threshold for CI testing.
MIN_CONFIDENCE_SCORE: 0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/production_e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
retention-days: 30
report-status:
needs: e2e
if: ${{ needs.e2e.result == 'failure' }}
if: failure()
uses: fingerprintjs/dx-team-toolkit/.github/workflows/report-workflow-status.yml@v1
with:
notification_title: 'Hey, <@U04GNKK5ZTQ>! Production e2e tests on demo.fingerprint.com have {status_message}'
Expand Down
55 changes: 37 additions & 18 deletions e2e/playground.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { Page, expect, test } from '@playwright/test';
import { PLAYGROUND_TAG } from '../src/client/components/playground/playgroundTags';
import { isAgentResponse, isServerResponse } from './zodUtils';
import { blockGoogleTagManager } from './e2eTestUtils';
import { TEST_IDS } from '../src/client/testIDs';

const TEST_ID = TEST_IDS.playground;

const getAgentResponse = async (page: Page) => {
const agentResponse =
(await (await page.getByTestId(PLAYGROUND_TAG.agentResponseJSON)).textContent()) ?? 'Agent response not found';
return JSON.parse(agentResponse);
const agentResponse = await page.getByTestId(TEST_ID.agentResponseJSON);
const agentResponseText = await agentResponse.textContent();
return agentResponseText ?? 'Agent response not found';
};

const getServerResponse = async (page: Page) => {
const serverResponse =
(await (await page.getByTestId(PLAYGROUND_TAG.serverResponseJSON)).textContent()) ?? 'Server response not found';
return JSON.parse(serverResponse);
const serverResponse = await page.getByTestId(TEST_ID.serverResponseJSON);
const serverResponseText = await serverResponse.textContent();
return serverResponseText ?? 'Server response not found';
};

function parseRequestId(inputString: string) {
const regex = /requestId:\s*"([^"]+)"/;
const match = inputString.match(regex);

if (match && match[1]) {
return match[1];
}
return null;
}

const clickPlaygroundRefreshButton = async (page: Page) => {
await page.getByTestId(PLAYGROUND_TAG.refreshButton).first().click();
await page.getByTestId(TEST_ID.refreshButton).first().click();
// Artificial wait necessary to make sure you get the updated response every time
await page.waitForTimeout(3000);
};
Expand All @@ -29,8 +40,8 @@ test.beforeEach(async ({ page }) => {
test.describe('Playground page', () => {
test('Page renders basic skeleton elements', async ({ page }) => {
await page.getByText('Fingerprint Pro Playground', { exact: true }).waitFor();
await page.getByText('Welcome, your visitor ID is').waitFor();
await page.getByTestId(PLAYGROUND_TAG.refreshButton).first().waitFor();
await page.getByText('Your Visitor ID is').waitFor();
await page.getByTestId(TEST_ID.refreshButton).first().waitFor();

await page.getByText('Identification', { exact: true }).waitFor();
await page.getByText('Smart signals', { exact: true }).waitFor();
Expand All @@ -41,7 +52,6 @@ test.describe('Playground page', () => {
});

test('Page renders signal tables', async ({ page }) => {
await page.getByText('Visitor ID', { exact: true }).waitFor();
await page.getByText('Last seen', { exact: true }).waitFor();
await page.getByText('Confidence Score', { exact: true }).waitFor();

Expand All @@ -54,28 +64,37 @@ test.describe('Playground page', () => {

test('Page renders agent response', async ({ page }) => {
const agentResponse = await getAgentResponse(page);
expect(isAgentResponse(agentResponse)).toBe(true);
expect(agentResponse).toContain('requestId');
expect(agentResponse).toContain('browserName');
expect(agentResponse).toContain('browserVersion');
expect(agentResponse).toContain('visitorId');
});

test('Page renders server response', async ({ page }) => {
const serverResponse = await getServerResponse(page);
expect(isServerResponse(serverResponse)).toBe(true);

expect(serverResponse).toContain('requestId');
expect(serverResponse).toContain('visitorId');
expect(serverResponse).toContain('incognito');
expect(serverResponse).toContain('botd');
expect(serverResponse).toContain('vpn');
expect(serverResponse).toContain('privacySettings');
});

test('Reload button updates agent response', async ({ page }) => {
const { requestId: oldRequestId } = await getAgentResponse(page);
const oldRequestId = parseRequestId(await getAgentResponse(page));
await clickPlaygroundRefreshButton(page);
const { requestId } = await getAgentResponse(page);
const requestId = parseRequestId(await getAgentResponse(page));

expect(oldRequestId).toHaveLength(20);
expect(requestId).toHaveLength(20);
expect(requestId).not.toEqual(oldRequestId);
});

test('Reload button updates server response', async ({ page }) => {
const oldRequestId = (await getServerResponse(page)).products.botd.data.requestId;
const oldRequestId = parseRequestId(await getServerResponse(page));
await clickPlaygroundRefreshButton(page);
const requestId = (await getServerResponse(page)).products.botd.data.requestId;
const requestId = parseRequestId(await getServerResponse(page));

expect(oldRequestId).toHaveLength(20);
expect(requestId).toHaveLength(20);
Expand Down
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@fingerprintjs/fingerprintjs-pro-react": "^2.6.2",
"@fingerprintjs/fingerprintjs-pro-server-api": "^3.1.0",
"@fingerprintjs/fingerprintjs-pro-server-api": "^4.1.2",
"@mui/icons-material": "^5.15.11",
"@mui/material": "^5.15.11",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slider": "^1.1.2",
Expand All @@ -37,7 +39,7 @@
"framer-motion": "^11.0.8",
"is-ip": "^5.0.1",
"leaflet": "^1.9.4",
"next": "^14.1.1",
"next": "^14.2.4",
"next-usequerystate": "^1.17.0",
"notistack": "^3.0.1",
"prismjs": "^1.29.0",
Expand All @@ -47,6 +49,7 @@
"react-query": "^3.39.3",
"react-syntax-highlighter": "^15.5.0",
"react-use": "^17.5.0",
"react18-json-view": "^0.2.9-canary.0",
"sequelize": "^6.37.1",
"sharp": "^0.33.2",
"twilio": "^5.0.1"
Expand Down Expand Up @@ -77,7 +80,7 @@
"swiper": "^11.0.7",
"tslib": "^2.6.2",
"tsx": "^4.7.1",
"typescript": "^5.3.3",
"typescript": "^5.5.3",
"vitest": "^1.3.1",
"zod": "^3.22.4"
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/appLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import { Layout } from '../Layout';

export default function LayoutUiInsideApp({ children }: { children: React.ReactNode }) {
const segments = useSelectedLayoutSegments();
return <Layout embed={segments.includes('embed')}>{children}</Layout>;
return <Layout embed={Boolean(segments?.includes('embed'))}>{children}</Layout>;
}
Loading

0 comments on commit abea4e7

Please sign in to comment.