Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement tests grouping #615

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/constants/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {DiffModes} from './diff-modes';
import {ViewMode} from './view-modes';
import {ReporterConfig} from '../types';
import {StoreReporterConfig} from '../types';
import {SaveFormat} from './save-formats';

export const CIRCLE_RADIUS = 150;

export const configDefaults: ReporterConfig = {
export const configDefaults: StoreReporterConfig = {
baseHost: '',
commandsWithShortHistory: [],
customGui: {},
Expand Down
2 changes: 2 additions & 0 deletions lib/constants/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const ERROR_TITLE_TEXT_LENGTH = 200;

export const NEW_ISSUE_LINK = 'https://github.com/gemini-testing/html-reporter/issues/new';

export const IMAGE_COMPARISON_FAILED_MESSAGE = 'image comparison failed';
13 changes: 11 additions & 2 deletions lib/db-utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _ from 'lodash';
import {logger} from '../common-utils';
import {DB_MAX_AVAILABLE_PAGE_SIZE, DB_SUITES_TABLE_NAME, SUITES_TABLE_COLUMNS, DB_COLUMN_INDEXES} from '../constants';
import {DbUrlsJsonData, RawSuitesRow, ReporterConfig} from '../types';
import type {Database, Statement} from 'better-sqlite3';
import type {Database as BetterSqlite3Database, Statement} from 'better-sqlite3';
import {ReadonlyDeep} from 'type-fest';

export const selectAllQuery = (tableName: string): string => `SELECT * FROM ${tableName}`;
Expand All @@ -16,10 +16,19 @@ export const compareDatabaseRowsByTimestamp = (row1: RawSuitesRow, row2: RawSuit
return (row1[DB_COLUMN_INDEXES.timestamp] as number) - (row2[DB_COLUMN_INDEXES.timestamp] as number);
};

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Database {}

export interface DbLoadResult {
url: string; status: string; data: null | unknown
}

export interface DbDetails {
url: string;
status: string;
success: boolean;
}

export interface HandleDatabasesOptions {
pluginConfig: ReporterConfig;
loadDbJsonUrl: (dbJsonUrl: string) => Promise<{data: DbUrlsJsonData | null; status?: string}>;
Expand Down Expand Up @@ -59,7 +68,7 @@ export const handleDatabases = async (dbJsonUrls: string[], opts: HandleDatabase
);
};

export const mergeTables = ({db, dbPaths, getExistingTables = (): string[] => []}: { db: Database, dbPaths: string[], getExistingTables?: (getTablesStatement: Statement<[]>) => string[] }): void => {
export const mergeTables = ({db, dbPaths, getExistingTables = (): string[] => []}: { db: BetterSqlite3Database, dbPaths: string[], getExistingTables?: (getTablesStatement: Statement<[]>) => string[] }): void => {
db.prepare(`PRAGMA page_size = ${DB_MAX_AVAILABLE_PAGE_SIZE}`).run();

for (const dbPath of dbPaths) {
Expand Down
14 changes: 12 additions & 2 deletions lib/gui/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ import {ServerArgs} from './index';
import {ServerReadyData} from './api';
import {ToolName} from '../constants';
import type {TestplaneToolAdapter} from '../adapters/tool/testplane';
import {ToolRunnerTree} from './tool-runner';

interface CustomGuiError {
response: {
status: number;
data: string;
}
}

export type GetInitResponse = (ToolRunnerTree & {customGuiError?: CustomGuiError}) | null;

export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
const {toolAdapter} = args;
Expand Down Expand Up @@ -54,7 +64,7 @@ export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
await (toolAdapter as TestplaneToolAdapter).initGuiHandler();
}

res.json(app.data);
res.json(app.data satisfies GetInitResponse);
} catch (e: unknown) {
const error = e as Error;
if (!app.data) {
Expand All @@ -68,7 +78,7 @@ export const start = async (args: ServerArgs): Promise<ServerReadyData> => {
data: `Error while trying to initialize custom GUI: ${error.message}`
}
}
});
} satisfies GetInitResponse);
}
});

Expand Down
2 changes: 1 addition & 1 deletion lib/static/components/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Gui extends Component {
};

componentDidMount() {
this.props.actions.initGuiReport();
this.props.actions.thunkInitGuiReport();
shadowusr marked this conversation as resolved.
Show resolved Hide resolved
this._subscribeToEvents();
}

Expand Down
2 changes: 1 addition & 1 deletion lib/static/components/report.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Report extends Component {
};

componentDidMount() {
this.props.actions.initStaticReport();
this.props.actions.thunkInitStaticReport();
}

componentWillUnmount() {
Expand Down
4 changes: 4 additions & 0 deletions lib/static/modules/action-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,13 @@ export default {
TOGGLE_GROUP_CHECKBOX: 'TOGGLE_GROUP_CHECKBOX',
UPDATE_BOTTOM_PROGRESS_BAR: 'UPDATE_BOTTOM_PROGRESS_BAR',
GROUP_TESTS_BY_KEY: 'GROUP_TESTS_BY_KEY',
GROUP_TESTS_SET_CURRENT_EXPRESSION: 'GROUP_TESTS_SET_CURRENT_EXPRESSION',
TOGGLE_BROWSER_CHECKBOX: 'TOGGLE_BROWSER_CHECKBOX',
SUITES_PAGE_SET_CURRENT_SUITE: 'SUITES_PAGE_SET_CURRENT_SUITE',
SUITES_PAGE_SET_SECTION_EXPANDED: 'SUITES_PAGE_SET_SECTION_EXPANDED',
SUITES_PAGE_SET_TREE_NODE_EXPANDED: 'SUITES_PAGE_SET_TREE_NODE_EXPANDED',
SUITES_PAGE_SET_ALL_TREE_NODES: 'SUITES_PAGE_SET_ALL_TREE_NODES',
SUITES_PAGE_REVEAL_TREE_NODE: 'SUITES_PAGE_REVEAL_TREE_NODE',
SUITES_PAGE_SET_STEPS_EXPANDED: 'SUITES_PAGE_SET_STEPS_EXPANDED',
VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE: 'VISUAL_CHECKS_PAGE_SET_CURRENT_NAMED_IMAGE',
UPDATE_LOADING_PROGRESS: 'UPDATE_LOADING_PROGRESS',
Expand Down
11 changes: 11 additions & 0 deletions lib/static/modules/actions/group-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import actionNames from '@/static/modules/action-names';
import {Action} from '@/static/modules/actions/types';

type SetCurrentGroupByExpressionAction = Action<typeof actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION, {
expressionIds: string[];
}>;

export const setCurrentGroupByExpression = (payload: SetCurrentGroupByExpressionAction['payload']): SetCurrentGroupByExpressionAction =>
({type: actionNames.GROUP_TESTS_SET_CURRENT_EXPRESSION, payload});

export type GroupTestsAction = SetCurrentGroupByExpressionAction;
98 changes: 3 additions & 95 deletions lib/static/modules/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import axios from 'axios';
import {isEmpty, difference} from 'lodash';
import {notify, dismissNotification as dismissNotify, POSITIONS} from 'reapop';
import {StaticTestsTreeBuilder} from '../../../tests-tree-builder/static';
import actionNames from '../action-names';
import {types as modalTypes} from '../../components/modals';
import {QUEUED} from '../../../constants/test-statuses';
import {DiffModes} from '../../../constants/diff-modes';
import {getHttpErrorMessage} from '../utils';
import {fetchDataFromDatabases, mergeDatabases, connectToDatabase, getMainDatabaseUrl, getSuitesTableRows} from '../../../db-utils/client';
import {connectToDatabase, getMainDatabaseUrl} from '../../../db-utils/client';
import {setFilteredBrowsers} from '../query-params';
import * as plugins from '../plugins';
import performanceMarks from '../../../constants/performance-marks';

export * from './lifecycle';
export * from './group-tests';
export * from './static-accepter';
export * from './suites-page';
export * from './suites';
Expand All @@ -35,97 +34,6 @@ export const createNotificationError = (id, error, props = {dismissAfter: 0}) =>

export const dismissNotification = dismissNotify;

export const initGuiReport = () => {
return async (dispatch) => {
performance?.mark?.(performanceMarks.JS_EXEC);
try {
const appState = await axios.get('/init');

const mainDatabaseUrl = getMainDatabaseUrl();
const db = await connectToDatabase(mainDatabaseUrl.href);

performance?.mark?.(performanceMarks.DBS_LOADED);

await plugins.loadAll(appState.data.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);

dispatch({
type: actionNames.INIT_GUI_REPORT,
payload: {...appState.data, db}
});

const {customGuiError} = appState.data;

if (customGuiError) {
dispatch(createNotificationError('initGuiReport', {...customGuiError}));
delete appState.data.customGuiError;
}
} catch (e) {
dispatch(createNotificationError('initGuiReport', e));
}
};
};

export const initStaticReport = () => {
return async dispatch => {
performance?.mark?.(performanceMarks.JS_EXEC);
const dataFromStaticFile = window.data || {};
let fetchDbDetails = [];
let db = null;

try {
const mainDatabaseUrls = new URL('databaseUrls.json', window.location.href);
const fetchDbResponses = await fetchDataFromDatabases([mainDatabaseUrls.href], (dbUrl, progress) => {
dispatch({
type: actionNames.UPDATE_LOADING_PROGRESS,
payload: {[dbUrl]: progress}
});
});

performance?.mark?.(performanceMarks.DBS_LOADED);

plugins.preloadAll(dataFromStaticFile.config);

fetchDbDetails = fetchDbResponses.map(({url, status, data}) => ({url, status, success: !!data}));

const dataForDbs = fetchDbResponses.map(({data}) => data).filter(data => data);

db = await mergeDatabases(dataForDbs);

performance?.mark?.(performanceMarks.DBS_MERGED);
} catch (e) {
dispatch(createNotificationError('initStaticReport', e));
}

await plugins.loadAll(dataFromStaticFile.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);
const testsTreeBuilder = StaticTestsTreeBuilder.create();

if (!db || isEmpty(fetchDbDetails)) {
return dispatch({
type: actionNames.INIT_STATIC_REPORT,
payload: {...dataFromStaticFile, db, fetchDbDetails, tree: testsTreeBuilder.build([]).tree, stats: {}, skips: [], browsers: []}
});
}

const suitesRows = getSuitesTableRows(db);

performance?.mark?.(performanceMarks.DB_EXTRACTED_ROWS);

const {tree, stats, skips, browsers} = testsTreeBuilder.build(suitesRows);

dispatch({
type: actionNames.INIT_STATIC_REPORT,
payload: {...dataFromStaticFile, db, fetchDbDetails, tree, stats, skips, browsers}
});
};
};

export const finGuiReport = () => ({type: actionNames.FIN_GUI_REPORT});
export const finStaticReport = () => ({type: actionNames.FIN_STATIC_REPORT});

const runTests = ({tests = [], action = {}} = {}) => {
return async (dispatch) => {
try {
Expand Down
143 changes: 143 additions & 0 deletions lib/static/modules/actions/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import axios from 'axios';
import {isEmpty} from 'lodash';

import performanceMarks from '@/constants/performance-marks';
import {
connectToDatabase, Database, DbDetails, DbLoadResult,
fetchDataFromDatabases,
getMainDatabaseUrl,
getSuitesTableRows,
mergeDatabases
} from '@/db-utils/client';
import * as plugins from '@/static/modules/plugins';
import actionNames from '@/static/modules/action-names';
import {FinalStats, SkipItem, StaticTestsTreeBuilder} from '@/tests-tree-builder/static';
import {createNotificationError} from '@/static/modules/actions/index';
import {Action, AppThunk} from '@/static/modules/actions/types';
import {DataForStaticFile} from '@/server-utils';
import {GetInitResponse} from '@/gui/server';
import {Tree} from '@/tests-tree-builder/base';
import {BrowserItem} from '@/types';

export type InitGuiReportAction = Action<typeof actionNames.INIT_GUI_REPORT, GetInitResponse & {db: Database; isNewUi?: boolean}>;
const initGuiReport = (payload: InitGuiReportAction['payload']): InitGuiReportAction =>
({type: actionNames.INIT_GUI_REPORT, payload});

interface InitGuiReportData {
isNewUi?: boolean;
}

export const thunkInitGuiReport = ({isNewUi}: InitGuiReportData = {}): AppThunk => {
return async (dispatch) => {
performance?.mark?.(performanceMarks.JS_EXEC);
try {
const appState = await axios.get<GetInitResponse>('/init');

if (!appState.data) {
throw new Error('Could not load app data. The report might be broken. Please check your project settings or try deleting results folder and relaunching UI server.');
}

const mainDatabaseUrl = getMainDatabaseUrl();
const db = await connectToDatabase(mainDatabaseUrl.href);

performance?.mark?.(performanceMarks.DBS_LOADED);

await plugins.loadAll(appState.data.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);

dispatch(initGuiReport({...appState.data, db, isNewUi}));

if (appState.data.customGuiError) {
const {customGuiError} = appState.data;

dispatch(createNotificationError('initGuiReport', {...customGuiError}));
delete appState.data.customGuiError;
}
} catch (e) {
dispatch(createNotificationError('initGuiReport', e));
}
};
};

export type InitStaticReportAction = Action<typeof actionNames.INIT_STATIC_REPORT,
Partial<DataForStaticFile> & {
db: Database;
fetchDbDetails: DbDetails[],
tree: Tree;
stats: FinalStats | null;
skips: SkipItem[];
browsers: BrowserItem[];
isNewUi?: boolean;
}>;
const initStaticReport = (payload: InitStaticReportAction['payload']): InitStaticReportAction =>
({type: actionNames.INIT_STATIC_REPORT, payload});

interface InitStaticReportData {
isNewUi?: boolean;
}

export const thunkInitStaticReport = ({isNewUi}: InitStaticReportData = {}): AppThunk => {
return async dispatch => {
performance?.mark?.(performanceMarks.JS_EXEC);
const dataFromStaticFile = (window as {data?: DataForStaticFile}).data || {} as Partial<DataForStaticFile>;

let fetchDbDetails: DbDetails[] = [];
let db = null;

try {
const mainDatabaseUrls = new URL('databaseUrls.json', window.location.href);
const fetchDbResponses = await fetchDataFromDatabases([mainDatabaseUrls.href], (dbUrl: string, progress: number) => {
dispatch({
type: actionNames.UPDATE_LOADING_PROGRESS,
payload: {[dbUrl]: progress}
});
}) as DbLoadResult[];

performance?.mark?.(performanceMarks.DBS_LOADED);

plugins.preloadAll(dataFromStaticFile.config);

fetchDbDetails = fetchDbResponses.map(({url, status, data}) => ({url, status, success: !!data}));

const dataForDbs = fetchDbResponses.map(({data}) => data).filter(data => data);

db = await mergeDatabases(dataForDbs);

performance?.mark?.(performanceMarks.DBS_MERGED);
} catch (e) {
dispatch(createNotificationError('thunkInitStaticReport', e));
}

await plugins.loadAll(dataFromStaticFile.config);

performance?.mark?.(performanceMarks.PLUGINS_LOADED);
const testsTreeBuilder = StaticTestsTreeBuilder.create();

if (!db || isEmpty(fetchDbDetails)) {
dispatch(initStaticReport({...dataFromStaticFile, db, fetchDbDetails, tree: testsTreeBuilder.build([]).tree, stats: null, skips: [], browsers: [], isNewUi}));

return;
}

const suitesRows = getSuitesTableRows(db);

performance?.mark?.(performanceMarks.DB_EXTRACTED_ROWS);

const {tree, stats, skips, browsers} = testsTreeBuilder.build(suitesRows);

dispatch(initStaticReport({...dataFromStaticFile, db, fetchDbDetails, tree, stats, skips, browsers, isNewUi}));
};
};

export type FinGuiReportAction = Action<typeof actionNames.FIN_GUI_REPORT>;
export const finGuiReport = (): FinGuiReportAction => ({type: actionNames.FIN_GUI_REPORT});

export type FinStaticReportAction = Action<typeof actionNames.FIN_STATIC_REPORT>;
export const finStaticReport = (): FinStaticReportAction => ({type: actionNames.FIN_STATIC_REPORT});

export type LifecycleAction =
| InitGuiReportAction
| InitStaticReportAction
| FinGuiReportAction
| FinStaticReportAction;
Loading
Loading