Skip to content

Commit

Permalink
Added playwright to test component render time.
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasbordeau committed May 6, 2024
1 parent f7f8279 commit 1e5152d
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 43 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# name: Playwright Tests
# on:
# push:
# branches: [ main, master ]
# pull_request:
# branches: [ main, master ]
# jobs:
# test:
# timeout-minutes: 60
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-node@v4
# with:
# node-version: lts/*
# - name: Install dependencies
# run: npm install -g yarn && yarn
# - name: Install Playwright Browsers
# run: yarn playwright install --with-deps
# - name: Run Playwright tests
# run: yarn playwright test
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: playwright-report/
# retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ storybook-static
*.tsbuildinfo
.eslintcache
.cache
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
"@nx/storybook": "18.3.3",
"@nx/vite": "18.3.3",
"@nx/web": "18.3.3",
"@playwright/test": "^1.43.1",
"@sentry/types": "^7.109.0",
"@storybook/addon-actions": "^7.6.3",
"@storybook/addon-coverage": "^1.0.0",
Expand Down Expand Up @@ -302,7 +303,6 @@
"msw": "^2.0.11",
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
"nx": "18.3.3",
"playwright": "^1.40.1",
"prettier": "^3.1.1",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
Expand Down
2 changes: 2 additions & 0 deletions packages/twenty-front/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ClientConfigProvider } from '@/client-config/components/ClientConfigPro
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
import { ProfilerReporter } from '@/debug/profiling/components/ProfilerReporter';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider';
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
Expand Down Expand Up @@ -43,6 +44,7 @@ root.render(
<CaptchaProvider>
<RecoilDebugObserverEffect />
<ApolloDevLogEffect />
<ProfilerReporter />
<BrowserRouter>
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<IconsProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useRecoilState } from 'recoil';

import { profilingSessionState } from '@/debug/profiling/states/profilingSessionState';

export const ProfilerReporter = () => {
const [profilingSession] = useRecoilState(profilingSessionState);

return (
<div
style={{ width: 0, height: 0, visibility: 'hidden' }}
data-profiling-report={JSON.stringify(profilingSession)}
id="profiling-report"
></div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Profiler, ProfilerOnRenderCallback } from 'react';
import { useRecoilCallback } from 'recoil';

import { profilingSessionState } from '@/debug/profiling/states/profilingSessionState';
import { ProfilingDataPoint } from '@/debug/profiling/types/ProfilingDataPoint';

export const ProfilerWrapper = ({
id,
componentName,
children,
}: {
id: string;
componentName: string;
children: React.ReactNode;
}) => {
const handleRender: ProfilerOnRenderCallback = useRecoilCallback(
({ set }) =>
(id, phase, actualDurationInMs) => {
const newDataPoint: ProfilingDataPoint = {
componentName,
id,
phase,
durationInMs: actualDurationInMs,
};

set(profilingSessionState, (currentProfilingSession) => ({
...currentProfilingSession,
[id]: [...(currentProfilingSession[id] ?? []), newDataPoint],
}));
},
[componentName],
);

return (
<Profiler id={id} onRender={handleRender}>
{children}
</Profiler>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { atom } from 'recoil';

import { ProfilingDataPoint } from '@/debug/profiling/types/ProfilingDataPoint';

export const profilingSessionState = atom<Record<string, ProfilingDataPoint[]>>(
{
key: 'profilingSessionState',
default: {},
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type ProfilingDataPoint = {
id: string;
componentName: string;
phase: string;
durationInMs: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOr
export const ChipFieldDisplay = () => {
const { record, generateRecordChipData } = useChipField();

if (!record) return null;
if (!record || !generateRecordChipData) return null;

const chipData = generateRecordChipData(record);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useContext } from 'react';
import { useInView } from 'react-intersection-observer';
import styled from '@emotion/styled';

import { ProfilerWrapper } from '@/debug/profiling/components/ProfilerWrapper';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/components/RecordTableCellFieldContextWrapper';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
Expand Down Expand Up @@ -42,43 +43,50 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
});

return (
<RecordTableRowContext.Provider
value={{
recordId,
rowIndex,
pathToShowPage:
getBasePathToShowPage({
objectNameSingular: objectMetadataItem.nameSingular,
}) + recordId,
isSelected: isRowSelected,
isReadOnly: objectMetadataItem.isRemote ?? false,
}}
>
<tr
ref={elementRef}
data-testid={`row-id-${recordId}`}
data-selectable-id={recordId}
<ProfilerWrapper id={`row-${rowIndex}`} componentName="RecordTableRow">
<RecordTableRowContext.Provider
value={{
recordId,
rowIndex,
pathToShowPage:
getBasePathToShowPage({
objectNameSingular: objectMetadataItem.nameSingular,
}) + recordId,
isSelected: isRowSelected,
isReadOnly: objectMetadataItem.isRemote ?? false,
}}
>
<StyledTd>
<CheckboxCell />
</StyledTd>
{inView
? visibleTableColumns.map((column, columnIndex) => (
<RecordTableCellContext.Provider
value={{
columnDefinition: column,
columnIndex,
}}
key={column.fieldMetadataId}
>
<RecordTableCellFieldContextWrapper />
</RecordTableCellContext.Provider>
))
: visibleTableColumns.map((column) => (
<td key={column.fieldMetadataId}></td>
))}
<td></td>
</tr>
</RecordTableRowContext.Provider>
<tr
ref={elementRef}
data-testid={`row-id-${recordId}`}
data-selectable-id={recordId}
>
<StyledTd>
<CheckboxCell />
</StyledTd>
{inView
? visibleTableColumns.map((column, columnIndex) => (
<ProfilerWrapper
id={`cell-${recordId}-${column.fieldMetadataId}`}
componentName="RecordTableCell"
>
<RecordTableCellContext.Provider
value={{
columnDefinition: column,
columnIndex,
}}
key={column.fieldMetadataId}
>
<RecordTableCellFieldContextWrapper />
</RecordTableCellContext.Provider>
</ProfilerWrapper>
))
: visibleTableColumns.map((column) => (
<td key={column.fieldMetadataId}></td>
))}
<td></td>
</tr>
</RecordTableRowContext.Provider>
</ProfilerWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ThemeProvider } from '@emotion/react';
import { THEME_DARK, THEME_LIGHT } from 'twenty-ui';

import { useColorScheme } from '../hooks/useColorScheme';
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';

import { useSystemColorScheme } from '../hooks/useSystemColorScheme';

type AppThemeProviderProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';

export const useSystemColorScheme = (): ColorScheme => {
const mediaQuery = useMemo(
() => window.matchMedia('(prefers-color-scheme: dark)'),
() => window.matchMedia?.('(prefers-color-scheme: dark)'),
[],
);

Expand Down
81 changes: 81 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3001',

extraHTTPHeaders: {
Authorization:
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsImlhdCI6MTcxNDU1OTA2NSwiZXhwIjoxNzIyMzM1MDY1fQ.3OkFv0F9Jp8aSb6esRRzG6s1eb8BRx6ND4pOPpkfOXc',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsImlhdCI6MTcxNDU1OTA2NSwiZXhwIjoxNzIyMzM1MDY1fQ.3OkFv0F9Jp8aSb6esRRzG6s1eb8BRx6ND4pOPpkfOXc" is used as
authorization header
.
},
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'yarn start',
// url: 'http://127.0.0.1:3001',
// reuseExistingServer: !process.env.CI,
// },
});

0 comments on commit 1e5152d

Please sign in to comment.