From cc7ea75440df571e28addcbf3023cbcbee4cf486 Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Sun, 5 Jul 2026 03:36:58 +0500 Subject: [PATCH 1/9] Added hook for timeline data --- src/hooks/useTimelineData.ts | 143 +++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/hooks/useTimelineData.ts diff --git a/src/hooks/useTimelineData.ts b/src/hooks/useTimelineData.ts new file mode 100644 index 00000000..a505b4a7 --- /dev/null +++ b/src/hooks/useTimelineData.ts @@ -0,0 +1,143 @@ +import { + type PickleTag, + type TestCaseStarted, + TestStepResultStatus, + TimeConversion, +} from "@cucumber/messages"; +import { useMemo } from "react"; + +import { useQueries } from "./useQueries.js"; +import { useSearch } from "./useSearch.js"; + +export interface TimelineItem { + readonly id: string; + readonly groupId: string; + readonly groupLabel: string; + readonly feature: string; + readonly scenario: string; + readonly tags: readonly PickleTag[]; + readonly status: TestStepResultStatus; + readonly start: number; + readonly end: number; + readonly testCaseStarted: TestCaseStarted; +} + +export interface TimelineGroup { + readonly id: string; + readonly label: string; +} + +export interface TimelineData { + readonly groups: readonly TimelineGroup[]; + readonly items: readonly TimelineItem[]; + readonly start: number; + readonly end: number; + readonly filtered: boolean; +} + +const UNASSIGNED_GROUP_ID = ""; + +export function useTimelineData(): TimelineData { + const { cucumberQuery } = useQueries(); + const { hideStatuses, tagExpression, searchTerm, unchanged } = useSearch(); + + return useMemo(() => { + const items: TimelineItem[] = []; + const groupIds = new Set(); + const normalizedSearchTerm = searchTerm?.trim().toLowerCase(); + + for (const testCaseFinished of cucumberQuery.findAllTestCaseFinished()) { + const testCaseStarted = + cucumberQuery.findTestCaseStartedBy(testCaseFinished); + if (!testCaseStarted) { + continue; + } + const pickle = cucumberQuery.findPickleBy(testCaseStarted); + if (!pickle) { + continue; + } + + // A test case with no step results at all is considered passed by definition + const status = + cucumberQuery.findMostSevereTestStepResultBy(testCaseFinished) + ?.status ?? TestStepResultStatus.PASSED; + + if (hideStatuses.includes(status)) { + continue; + } + + if (tagExpression) { + const tagNames = pickle.tags.map((tag) => tag.name); + if (!tagExpression.evaluate(tagNames)) { + continue; + } + } + + const feature = + cucumberQuery.findLineageBy(testCaseStarted)?.feature?.name ?? + ""; + const scenario = pickle.name; + + if ( + normalizedSearchTerm && + !`${feature} ${scenario}` + .toLowerCase() + .includes(normalizedSearchTerm) + ) { + continue; + } + + const groupId = testCaseStarted.workerId ?? UNASSIGNED_GROUP_ID; + groupIds.add(groupId); + + items.push({ + id: testCaseStarted.id, + groupId, + groupLabel: describeGroup(groupId), + feature, + scenario, + tags: pickle.tags, + status, + start: TimeConversion.timestampToMillisecondsSinceEpoch( + testCaseStarted.timestamp, + ), + end: TimeConversion.timestampToMillisecondsSinceEpoch( + testCaseFinished.timestamp, + ), + testCaseStarted, + }); + } + + items.sort((a, b) => a.start - b.start || a.end - b.end); + + const groups: TimelineGroup[] = [...groupIds] + .sort(compareGroupIds) + .map((id) => ({ id, label: describeGroup(id) })); + + const start = + items.length > 0 ? Math.min(...items.map((item) => item.start)) : 0; + const end = + items.length > 0 ? Math.max(...items.map((item) => item.end)) : 0; + + return { groups, items, start, end, filtered: !unchanged }; + }, [cucumberQuery, hideStatuses, tagExpression, searchTerm, unchanged]); +} + +function describeGroup(id: string): string { + return id === UNASSIGNED_GROUP_ID ? "Main process" : `Worker ${id}`; +} + +function compareGroupIds(a: string, b: string): number { + if (a === UNASSIGNED_GROUP_ID) { + return -1; + } + if (b === UNASSIGNED_GROUP_ID) { + return 1; + } + const aNum = Number(a); + const bNum = Number(b); + if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) { + return aNum - bNum; + } + return a.localeCompare(b); +} From 37f899c49cc95c1272fbd7a295c4571a31c0934b Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Sun, 5 Jul 2026 03:41:53 +0500 Subject: [PATCH 2/9] Created React component for Timeline --- src/components/app/Timeline.module.scss | 174 ++++++++++++++++++++++++ src/components/app/Timeline.tsx | 136 ++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 src/components/app/Timeline.module.scss create mode 100644 src/components/app/Timeline.tsx diff --git a/src/components/app/Timeline.module.scss b/src/components/app/Timeline.module.scss new file mode 100644 index 00000000..3cb8622b --- /dev/null +++ b/src/components/app/Timeline.module.scss @@ -0,0 +1,174 @@ +@use '../../styles/statuses'; +@use '../../styles/theming'; + +.container { + display: flex; + flex-direction: column; + gap: 1em; +} + +.empty { + font-style: italic; +} + +.chart { + position: relative; + overflow-x: auto; +} + +.axis { + position: relative; + height: 1.5em; + margin: 0 0 0.5em; + padding: 0; + list-style: none; + border-bottom: 1px solid theming.$panelAccentColor; + min-width: 40em; +} + +.tick { + position: absolute; + top: 0; + bottom: 0; + border-left: 1px dashed theming.$panelAccentColor; + padding-left: 0.35em; + font-size: 0.75em; + opacity: 0.75; + white-space: nowrap; + + &[data-edge='end'] { + border-left: none; + border-right: 1px dashed theming.$panelAccentColor; + padding-left: 0; + padding-right: 0.35em; + text-align: right; + + span { + display: inline-block; + transform: translateX(-100%); + } + } +} + +.groups { + display: flex; + flex-direction: column; + gap: 0.25em; + padding: 0; + margin: 0; + list-style: none; + min-width: 40em; +} + +.group { + display: flex; + align-items: center; + gap: 0.5em; +} + +.groupLabel { + flex: 0 0 auto; + width: 8em; + font-size: 0.85em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: 0.75; +} + +.lane { + position: relative; + flex: 1 1 auto; + height: 2.25em; + background-color: theming.$panelBackgroundColor; + border-radius: 0.25em; +} + +.item { + position: absolute; + top: 0.25em; + bottom: 0.25em; + min-width: 6px; + padding: 0 0.4em; + overflow: hidden; + border: none; + border-radius: 0.2em; + cursor: pointer; + color: white; + font: inherit; + font-size: 0.75em; + text-align: left; + + @each $name, $color in statuses.$statusColors { + &[data-status='#{$name}'] { + background-color: $color; + } + } + + &[aria-pressed='true'] { + outline: 2px solid theming.$panelTextColor; + outline-offset: 1px; + z-index: 1; + } +} + +.itemLabel { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.detail { + position: relative; + padding: 1em; + background-color: theming.$panelBackgroundColor; + color: theming.$panelTextColor; + border: 1px solid theming.$panelAccentColor; + border-radius: 0.25em; +} + +.detailClose { + position: absolute; + top: 0.5em; + right: 0.5em; + padding: 0.25em; + background: none; + border: none; + cursor: pointer; + color: inherit; +} + +.detailTitle { + display: flex; + align-items: center; + gap: 0.4em; + margin: 0 0 0.25em; + font-size: 1.1em; + + svg { + height: 1em; + } +} + +.detailFeature { + margin: 0 0 0.5em; + opacity: 0.75; +} + +.detailMeta { + display: flex; + flex-wrap: wrap; + gap: 1em; + margin: 0.75em 0 0; + + dt { + font-size: 0.75em; + text-transform: uppercase; + opacity: 0.75; + } + + dd { + margin: 0; + } +} \ No newline at end of file diff --git a/src/components/app/Timeline.tsx b/src/components/app/Timeline.tsx new file mode 100644 index 00000000..e121fc4b --- /dev/null +++ b/src/components/app/Timeline.tsx @@ -0,0 +1,136 @@ +import { faXmark } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { type FC, useState } from 'react' + +import { formatExecutionDuration } from '../../formatExecutionDuration.js' +import { type TimelineItem, useTimelineData } from '../../hooks/useTimelineData.js' +import { StatusIcon } from '../gherkin/StatusIcon.js' +import statusName from '../gherkin/statusName.js' +import { Tags } from '../gherkin/Tags.js' +import styles from './Timeline.module.scss' + +const AXIS_TICKS = 4 + +export const Timeline: FC = () => { + const { groups, items, start, end, filtered } = useTimelineData() + const [selectedId, setSelectedId] = useState() + + if (items.length === 0) { + return filtered ? ( +

No scenarios match your query and/or filters.

+ ) : ( +

No scenarios were executed.

+ ) + } + + const duration = Math.max(end - start, 1) + const ticks = Array.from({ length: AXIS_TICKS + 1 }, (_, index) => { + const offset = (duration / AXIS_TICKS) * index + return { + index, + position: (offset / duration) * 100, + label: formatExecutionDuration(new Date(start), new Date(start + offset)), + } + }) + const selectedItem = items.find((item) => item.id === selectedId) + + return ( +
+
+ +
    + {groups.map((group) => ( +
  1. + {group.label} +
    + {items + .filter((item) => item.groupId === group.id) + .map((item) => ( + + setSelectedId((current) => (current === item.id ? undefined : item.id)) + } + /> + ))} +
    +
  2. + ))} +
+
+ {selectedItem && ( + setSelectedId(undefined)} /> + )} +
+ ) +} + +const TimelineBar: FC<{ + item: TimelineItem + rangeStart: number + duration: number + selected: boolean + onSelect: () => void +}> = ({ item, rangeStart, duration, selected, onSelect }) => { + const left = ((item.start - rangeStart) / duration) * 100 + const width = Math.max(((item.end - item.start) / duration) * 100, 0.3) + return ( + + ) +} + +const TimelineDetail: FC<{ item: TimelineItem; onClose: () => void }> = ({ item, onClose }) => { + return ( +
+ +

+ + {item.scenario} +

+ {item.feature &&

{item.feature}

} + +
+
+
Status
+
{statusName(item.status)}
+
+
+
Duration
+
{formatExecutionDuration(new Date(item.start), new Date(item.end))}
+
+
+
Worker
+
{item.groupLabel}
+
+
+
+ ) +} From a3aa4afc1149c24a45fe1fb1f77b17441a12c215 Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Sun, 5 Jul 2026 03:46:38 +0500 Subject: [PATCH 3/9] Added tests for Timeline component --- src/components/app/Report.tsx | 5 + src/components/app/Timeline.spec.tsx | 136 ++++++++++++++++++++++++ src/components/app/Timeline.stories.tsx | 71 +++++++++++++ src/components/app/index.ts | 1 + 4 files changed, 213 insertions(+) create mode 100644 src/components/app/Timeline.spec.tsx create mode 100644 src/components/app/Timeline.stories.tsx diff --git a/src/components/app/Report.tsx b/src/components/app/Report.tsx index f6e2a61c..2eb9f50d 100644 --- a/src/components/app/Report.tsx +++ b/src/components/app/Report.tsx @@ -5,6 +5,7 @@ import { FilteredDocuments } from './FilteredDocuments.js' import styles from './Report.module.scss' import { SearchBar } from './SearchBar.js' import { TestRunHooks } from './TestRunHooks.js' +import { Timeline } from './Timeline.js' export const Report: FC = () => { return ( @@ -13,6 +14,10 @@ export const Report: FC = () => { +
+

Timeline

+ +

Scenarios

diff --git a/src/components/app/Timeline.spec.tsx b/src/components/app/Timeline.spec.tsx new file mode 100644 index 00000000..dfdeeb23 --- /dev/null +++ b/src/components/app/Timeline.spec.tsx @@ -0,0 +1,136 @@ +import { type Envelope, type TestCaseStarted, TestStepResultStatus } from '@cucumber/messages' +import { render, screen, within } from '@testing-library/react' +import { userEvent } from '@testing-library/user-event' +import { expect } from 'chai' + +import examplesTablesFeature from '../../../acceptance/examples-tables/examples-tables.js' +import { ControlledSearchProvider } from './ControlledSearchProvider.js' +import { EnvelopesProvider } from './EnvelopesProvider.js' +import { Timeline } from './Timeline.js' + +describe('', () => { + it('should show a message when no scenarios were executed', () => { + render( + + {}}> + + + + ) + + expect(screen.getByText('No scenarios were executed.')).to.be.visible + }) + + it('should show a message when filters exclude every scenario', () => { + render( + + {}} + > + + + + ) + + expect(screen.getByText('No scenarios match your query and/or filters.')).to.be.visible + }) + + it('should render one bar per executed scenario, in a single lane when no worker information is present', () => { + render( + + {}}> + + + + ) + + expect(screen.getByText('Main process')).to.be.visible + expect(screen.getAllByRole('button')).to.have.length(7) + }) + + it('should respect the hideStatuses filter from the shared search context', () => { + render( + + {}} + > + + + + ) + + expect(screen.getAllByRole('button')).to.have.length(5) + }) + + it('should respect a tag expression from the shared search context', () => { + render( + + {}} + > + + + + ) + + expect(screen.getAllByRole('button')).to.have.length(2) + }) + + it('should group test cases by worker id, sorted numerically', () => { + render( + + {}}> + + + + ) + + expect(screen.getAllByTestId('cucumber.timeline.group')).to.have.length(2) + expect(screen.getByText('Worker 0')).to.be.visible + expect(screen.getByText('Worker 1')).to.be.visible + }) + + it('should show scenario details when a bar is selected, and hide them again on close', async () => { + render( + + {}}> + + + + ) + + expect(screen.queryByTestId('cucumber.timeline.detail')).to.be.null + + await userEvent.click(screen.getByRole('button', { name: 'Eating cucumbers with 11 friends' })) + + const detail = screen.getByTestId('cucumber.timeline.detail') + expect(within(detail).getByText('Eating cucumbers with 11 friends')).to.be.visible + expect(within(detail).getByText('Examples Tables')).to.be.visible + + await userEvent.click(within(detail).getByRole('button', { name: 'Close' })) + + expect(screen.queryByTestId('cucumber.timeline.detail')).to.be.null + }) +}) + +function distributeAcrossWorkers( + envelopes: ReadonlyArray, + workerCount: number +): ReadonlyArray { + let index = 0 + return envelopes.map((envelope): Envelope => { + if (!envelope.testCaseStarted) { + return envelope + } + const workerId = String(index % workerCount) + index += 1 + const testCaseStarted: TestCaseStarted = { ...envelope.testCaseStarted, workerId } + return { ...envelope, testCaseStarted } + }) +} diff --git a/src/components/app/Timeline.stories.tsx b/src/components/app/Timeline.stories.tsx new file mode 100644 index 00000000..ff21998c --- /dev/null +++ b/src/components/app/Timeline.stories.tsx @@ -0,0 +1,71 @@ +import { type Envelope, type TestCaseStarted, TimeConversion } from '@cucumber/messages' +import type { Story } from '@ladle/react' + +import examplesTablesFeature from '../../../acceptance/examples-tables/examples-tables.js' +import { EnvelopesProvider } from './EnvelopesProvider.js' +import { InMemorySearchProvider } from './InMemorySearchProvider.js' +import { Timeline } from './Timeline.js' + +export default { + title: 'App/Timeline', +} + +type TemplateArgs = { + envelopes: readonly Envelope[] +} + +const Template: Story = ({ envelopes }) => { + return ( + + + + + + ) +} + +export const SingleProcess = Template.bind({}) +SingleProcess.args = { + envelopes: examplesTablesFeature, +} as TemplateArgs + +export const Parallel = Template.bind({}) +Parallel.args = { + envelopes: distributeAcrossWorkers(examplesTablesFeature, 3), +} as TemplateArgs + +export const NoTestCases = Template.bind({}) +NoTestCases.args = { + envelopes: [ + { testRunStarted: { timestamp: TimeConversion.millisecondsSinceEpochToTimestamp(0) } }, + { + testRunFinished: { + timestamp: TimeConversion.millisecondsSinceEpochToTimestamp(1000), + success: true, + }, + }, + ], +} as TemplateArgs + +/** + * Cucumber implementations report which worker ran a test case via + * `TestCaseStarted.workerId`. The compatibility-kit fixtures used in this story + * were captured from a single-process run so this helper distributes the + * existing test cases across a number of synthetic workers to demonstrate how + * the timeline renders parallel execution. + */ +function distributeAcrossWorkers( + envelopes: ReadonlyArray, + workerCount: number +): ReadonlyArray { + let index = 0 + return envelopes.map((envelope): Envelope => { + if (!envelope.testCaseStarted) { + return envelope + } + const workerId = String(index % workerCount) + index += 1 + const testCaseStarted: TestCaseStarted = { ...envelope.testCaseStarted, workerId } + return { ...envelope, testCaseStarted } + }) +} \ No newline at end of file diff --git a/src/components/app/index.ts b/src/components/app/index.ts index 890c590d..6c3b0676 100644 --- a/src/components/app/index.ts +++ b/src/components/app/index.ts @@ -12,3 +12,4 @@ export * from './SearchBar.js' export * from './StatusesSummary.js' export * from './TestRunHooks.js' export * from './UrlSearchProvider.js' +export * from './Timeline.js' From c17bb022562eaddf0dfb4c2f5866dc718ecc54cf Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Sun, 5 Jul 2026 03:51:39 +0500 Subject: [PATCH 4/9] Updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80bd6c78..84d10cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -583,3 +583,7 @@ to rebuild them every time the envelope list is updated. Use this instead of `` component showing scenario execution over time grouped by worker ported from cucumber-jvm's TimelineFormatter ([#126](https://github.com/cucumber/react-components/issues/126)) From b96cd7edd3dbc946331d1e0a1a40e2a8e2d2c2df Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Tue, 7 Jul 2026 01:44:25 +0500 Subject: [PATCH 5/9] Added Tabs in Report to switch between two formats --- src/components/app/Report.module.scss | 36 +++++++++++++++++++++++++++ src/components/app/Report.tsx | 27 ++++++++++++++------ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/components/app/Report.module.scss b/src/components/app/Report.module.scss index b8d3c8f4..dfbff60e 100644 --- a/src/components/app/Report.module.scss +++ b/src/components/app/Report.module.scss @@ -1,3 +1,5 @@ +@use '../../styles/theming'; + .layout { > section:not(:last-child) { margin-bottom: 1.5em; @@ -8,3 +10,37 @@ font-size: 1.25em; margin: 1em 0; } + +.tabList { + display: flex; + gap: 0.25em; + border-bottom: 1px solid theming.$panelAccentColor; + margin-bottom: 1em; +} + +.tab { + padding: 0.5em 1em; + border-bottom: 2px solid transparent; + color: theming.$panelTextColor; + cursor: pointer; + outline: none; + + &[data-hovered] { + background-color: theming.$panelBackgroundColor; + } + + &[data-selected] { + border-bottom-color: theming.$anchorColor; + color: theming.$anchorColor; + font-weight: 600; + } + + &[data-focus-visible] { + outline: 2px solid theming.$anchorColor; + outline-offset: 2px; + } +} + +.tabPanel { + outline: none; +} \ No newline at end of file diff --git a/src/components/app/Report.tsx b/src/components/app/Report.tsx index 2eb9f50d..0bcabc46 100644 --- a/src/components/app/Report.tsx +++ b/src/components/app/Report.tsx @@ -1,4 +1,5 @@ import type { FC } from 'react' +import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'react-aria-components' import { ExecutionSummary } from './ExecutionSummary.js' import { FilteredDocuments } from './FilteredDocuments.js' @@ -15,12 +16,24 @@ export const Report: FC = () => {
-

Timeline

- -
-
-

Scenarios

- + + + + Scenarios + + + Timeline + + + + + + + + + + +

Hooks

@@ -28,4 +41,4 @@ export const Report: FC = () => {
) -} +} \ No newline at end of file From ad1238db20f14e10dc2ba179fd222fb04abd7127 Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Tue, 7 Jul 2026 13:59:38 +0500 Subject: [PATCH 6/9] style: fix lint formatting issues --- src/components/app/Report.tsx | 2 +- src/components/app/Timeline.stories.tsx | 2 +- src/components/app/index.ts | 2 +- src/hooks/useTimelineData.ts | 223 +++++++++++------------- 4 files changed, 109 insertions(+), 120 deletions(-) diff --git a/src/components/app/Report.tsx b/src/components/app/Report.tsx index 0bcabc46..5fa206b9 100644 --- a/src/components/app/Report.tsx +++ b/src/components/app/Report.tsx @@ -41,4 +41,4 @@ export const Report: FC = () => { ) -} \ No newline at end of file +} diff --git a/src/components/app/Timeline.stories.tsx b/src/components/app/Timeline.stories.tsx index ff21998c..af99a13f 100644 --- a/src/components/app/Timeline.stories.tsx +++ b/src/components/app/Timeline.stories.tsx @@ -68,4 +68,4 @@ function distributeAcrossWorkers( const testCaseStarted: TestCaseStarted = { ...envelope.testCaseStarted, workerId } return { ...envelope, testCaseStarted } }) -} \ No newline at end of file +} diff --git a/src/components/app/index.ts b/src/components/app/index.ts index 6c3b0676..b6399b7b 100644 --- a/src/components/app/index.ts +++ b/src/components/app/index.ts @@ -11,5 +11,5 @@ export * from './Report.js' export * from './SearchBar.js' export * from './StatusesSummary.js' export * from './TestRunHooks.js' -export * from './UrlSearchProvider.js' export * from './Timeline.js' +export * from './UrlSearchProvider.js' diff --git a/src/hooks/useTimelineData.ts b/src/hooks/useTimelineData.ts index a505b4a7..0385a03d 100644 --- a/src/hooks/useTimelineData.ts +++ b/src/hooks/useTimelineData.ts @@ -1,143 +1,132 @@ import { - type PickleTag, - type TestCaseStarted, - TestStepResultStatus, - TimeConversion, -} from "@cucumber/messages"; -import { useMemo } from "react"; + type PickleTag, + type TestCaseStarted, + TestStepResultStatus, + TimeConversion, +} from '@cucumber/messages' +import { useMemo } from 'react' -import { useQueries } from "./useQueries.js"; -import { useSearch } from "./useSearch.js"; +import { useQueries } from './useQueries.js' +import { useSearch } from './useSearch.js' export interface TimelineItem { - readonly id: string; - readonly groupId: string; - readonly groupLabel: string; - readonly feature: string; - readonly scenario: string; - readonly tags: readonly PickleTag[]; - readonly status: TestStepResultStatus; - readonly start: number; - readonly end: number; - readonly testCaseStarted: TestCaseStarted; + readonly id: string + readonly groupId: string + readonly groupLabel: string + readonly feature: string + readonly scenario: string + readonly tags: readonly PickleTag[] + readonly status: TestStepResultStatus + readonly start: number + readonly end: number + readonly testCaseStarted: TestCaseStarted } export interface TimelineGroup { - readonly id: string; - readonly label: string; + readonly id: string + readonly label: string } export interface TimelineData { - readonly groups: readonly TimelineGroup[]; - readonly items: readonly TimelineItem[]; - readonly start: number; - readonly end: number; - readonly filtered: boolean; + readonly groups: readonly TimelineGroup[] + readonly items: readonly TimelineItem[] + readonly start: number + readonly end: number + readonly filtered: boolean } -const UNASSIGNED_GROUP_ID = ""; +const UNASSIGNED_GROUP_ID = '' export function useTimelineData(): TimelineData { - const { cucumberQuery } = useQueries(); - const { hideStatuses, tagExpression, searchTerm, unchanged } = useSearch(); - - return useMemo(() => { - const items: TimelineItem[] = []; - const groupIds = new Set(); - const normalizedSearchTerm = searchTerm?.trim().toLowerCase(); - - for (const testCaseFinished of cucumberQuery.findAllTestCaseFinished()) { - const testCaseStarted = - cucumberQuery.findTestCaseStartedBy(testCaseFinished); - if (!testCaseStarted) { - continue; - } - const pickle = cucumberQuery.findPickleBy(testCaseStarted); - if (!pickle) { - continue; - } - - // A test case with no step results at all is considered passed by definition - const status = - cucumberQuery.findMostSevereTestStepResultBy(testCaseFinished) - ?.status ?? TestStepResultStatus.PASSED; - - if (hideStatuses.includes(status)) { - continue; - } - - if (tagExpression) { - const tagNames = pickle.tags.map((tag) => tag.name); - if (!tagExpression.evaluate(tagNames)) { - continue; - } - } - - const feature = - cucumberQuery.findLineageBy(testCaseStarted)?.feature?.name ?? - ""; - const scenario = pickle.name; - - if ( - normalizedSearchTerm && - !`${feature} ${scenario}` - .toLowerCase() - .includes(normalizedSearchTerm) - ) { - continue; - } - - const groupId = testCaseStarted.workerId ?? UNASSIGNED_GROUP_ID; - groupIds.add(groupId); - - items.push({ - id: testCaseStarted.id, - groupId, - groupLabel: describeGroup(groupId), - feature, - scenario, - tags: pickle.tags, - status, - start: TimeConversion.timestampToMillisecondsSinceEpoch( - testCaseStarted.timestamp, - ), - end: TimeConversion.timestampToMillisecondsSinceEpoch( - testCaseFinished.timestamp, - ), - testCaseStarted, - }); + const { cucumberQuery } = useQueries() + const { hideStatuses, tagExpression, searchTerm, unchanged } = useSearch() + + return useMemo(() => { + const items: TimelineItem[] = [] + const groupIds = new Set() + const normalizedSearchTerm = searchTerm?.trim().toLowerCase() + + for (const testCaseFinished of cucumberQuery.findAllTestCaseFinished()) { + const testCaseStarted = cucumberQuery.findTestCaseStartedBy(testCaseFinished) + if (!testCaseStarted) { + continue + } + const pickle = cucumberQuery.findPickleBy(testCaseStarted) + if (!pickle) { + continue + } + + // A test case with no step results at all is considered passed by definition + const status = + cucumberQuery.findMostSevereTestStepResultBy(testCaseFinished)?.status ?? + TestStepResultStatus.PASSED + + if (hideStatuses.includes(status)) { + continue + } + + if (tagExpression) { + const tagNames = pickle.tags.map((tag) => tag.name) + if (!tagExpression.evaluate(tagNames)) { + continue } + } + + const feature = cucumberQuery.findLineageBy(testCaseStarted)?.feature?.name ?? '' + const scenario = pickle.name + + if ( + normalizedSearchTerm && + !`${feature} ${scenario}`.toLowerCase().includes(normalizedSearchTerm) + ) { + continue + } + + const groupId = testCaseStarted.workerId ?? UNASSIGNED_GROUP_ID + groupIds.add(groupId) + + items.push({ + id: testCaseStarted.id, + groupId, + groupLabel: describeGroup(groupId), + feature, + scenario, + tags: pickle.tags, + status, + start: TimeConversion.timestampToMillisecondsSinceEpoch(testCaseStarted.timestamp), + end: TimeConversion.timestampToMillisecondsSinceEpoch(testCaseFinished.timestamp), + testCaseStarted, + }) + } - items.sort((a, b) => a.start - b.start || a.end - b.end); + items.sort((a, b) => a.start - b.start || a.end - b.end) - const groups: TimelineGroup[] = [...groupIds] - .sort(compareGroupIds) - .map((id) => ({ id, label: describeGroup(id) })); + const groups: TimelineGroup[] = [...groupIds] + .sort(compareGroupIds) + .map((id) => ({ id, label: describeGroup(id) })) - const start = - items.length > 0 ? Math.min(...items.map((item) => item.start)) : 0; - const end = - items.length > 0 ? Math.max(...items.map((item) => item.end)) : 0; + const start = items.length > 0 ? Math.min(...items.map((item) => item.start)) : 0 + const end = items.length > 0 ? Math.max(...items.map((item) => item.end)) : 0 - return { groups, items, start, end, filtered: !unchanged }; - }, [cucumberQuery, hideStatuses, tagExpression, searchTerm, unchanged]); + return { groups, items, start, end, filtered: !unchanged } + }, [cucumberQuery, hideStatuses, tagExpression, searchTerm, unchanged]) } function describeGroup(id: string): string { - return id === UNASSIGNED_GROUP_ID ? "Main process" : `Worker ${id}`; + return id === UNASSIGNED_GROUP_ID ? 'Main process' : `Worker ${id}` } function compareGroupIds(a: string, b: string): number { - if (a === UNASSIGNED_GROUP_ID) { - return -1; - } - if (b === UNASSIGNED_GROUP_ID) { - return 1; - } - const aNum = Number(a); - const bNum = Number(b); - if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) { - return aNum - bNum; - } - return a.localeCompare(b); + if (a === UNASSIGNED_GROUP_ID) { + return -1 + } + if (b === UNASSIGNED_GROUP_ID) { + return 1 + } + const aNum = Number(a) + const bNum = Number(b) + if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) { + return aNum - bNum + } + return a.localeCompare(b) } From e7324685c1e39d40eae670ce7a31f5c62ab60ae0 Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Tue, 7 Jul 2026 18:47:46 +0500 Subject: [PATCH 7/9] Added sample parallel run envelope --- samples/parallel-run.ts | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 samples/parallel-run.ts diff --git a/samples/parallel-run.ts b/samples/parallel-run.ts new file mode 100644 index 00000000..7032c134 --- /dev/null +++ b/samples/parallel-run.ts @@ -0,0 +1,63 @@ +import type { Envelope } from '@cucumber/messages' + +export default [ + {"meta":{"protocolVersion":"29.0.1","implementation":{"name":"cucumber-jvm","version":"7.30.0"},"runtime":{"name":"Java HotSpot(TM) 64-Bit Server VM","version":"26.0.1+8-34"},"os":{"name":"Linux"},"cpu":{"name":"amd64"}}} +,{"testRunStarted":{"timestamp":{"seconds":1783364910,"nanos":419492005}}} +,{"source":{"uri":"classpath:parallel/scenarios.feature","data":"Feature: Parallel Scenarios\n\n Scenario: One\n Given I wait for 5 seconds\n\n Scenario: Two\n Given I wait for 10 seconds\n\n Scenario: Three\n Given I wait for 15 seconds\n\n Scenario: Four\n Given I wait for 20 seconds","mediaType":"text/x.cucumber.gherkin+plain"}} +,{"gherkinDocument":{"uri":"classpath:parallel/scenarios.feature","feature":{"location":{"line":1,"column":1},"tags":[],"language":"en","keyword":"Feature","name":"Parallel Scenarios","description":"","children":[{"scenario":{"location":{"line":3,"column":3},"tags":[],"keyword":"Scenario","name":"One","description":"","steps":[{"location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 5 seconds","id":"ec7dd26c-eff5-4987-9e49-e4d6ebc47367"}],"examples":[],"id":"5ecdab5f-6f2e-4dbc-99bd-5d34087df0db"}},{"scenario":{"location":{"line":6,"column":3},"tags":[],"keyword":"Scenario","name":"Two","description":"","steps":[{"location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 10 seconds","id":"aad30de0-0f9d-4aee-aab6-4c9b89d2dc82"}],"examples":[],"id":"9ec6892a-20d6-4179-bc9b-0a5ee5352fcf"}},{"scenario":{"location":{"line":9,"column":3},"tags":[],"keyword":"Scenario","name":"Three","description":"","steps":[{"location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 15 seconds","id":"27fb1e9b-2da9-44df-9a40-3b9cd5f255dd"}],"examples":[],"id":"73fddbfe-bed6-4b96-b134-2e40bcbcbe77"}},{"scenario":{"location":{"line":12,"column":3},"tags":[],"keyword":"Scenario","name":"Four","description":"","steps":[{"location":{"line":13,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 20 seconds","id":"23b79b90-7ae6-4614-8790-c5b01ed66d9e"}],"examples":[],"id":"d2e50951-bc48-4c28-b513-0e395ac0a146"}}]},"comments":[]}} +,{"pickle":{"id":"8f92f4e6-d3a5-4db6-bfc7-ef4cd7ab7104","uri":"classpath:parallel/scenarios.feature","name":"One","language":"en","steps":[{"astNodeIds":["ec7dd26c-eff5-4987-9e49-e4d6ebc47367"],"id":"5f9baeb1-a1c0-4e58-9dbc-83e64836c83c","type":"Context","text":"I wait for 5 seconds"}],"tags":[],"astNodeIds":["5ecdab5f-6f2e-4dbc-99bd-5d34087df0db"]}} +,{"pickle":{"id":"ab4838f7-ff58-4ac6-885a-f9532b218146","uri":"classpath:parallel/scenarios.feature","name":"Two","language":"en","steps":[{"astNodeIds":["aad30de0-0f9d-4aee-aab6-4c9b89d2dc82"],"id":"ae6e653e-d568-4c33-a99d-0653393bf03a","type":"Context","text":"I wait for 10 seconds"}],"tags":[],"astNodeIds":["9ec6892a-20d6-4179-bc9b-0a5ee5352fcf"]}} +,{"pickle":{"id":"742927e4-043d-404c-aea3-83edf921a0aa","uri":"classpath:parallel/scenarios.feature","name":"Three","language":"en","steps":[{"astNodeIds":["27fb1e9b-2da9-44df-9a40-3b9cd5f255dd"],"id":"a96d5267-582e-4857-9d96-6816dc3e4735","type":"Context","text":"I wait for 15 seconds"}],"tags":[],"astNodeIds":["73fddbfe-bed6-4b96-b134-2e40bcbcbe77"]}} +,{"pickle":{"id":"90869e14-d1a1-46f8-b959-5e8d937772dd","uri":"classpath:parallel/scenarios.feature","name":"Four","language":"en","steps":[{"astNodeIds":["23b79b90-7ae6-4614-8790-c5b01ed66d9e"],"id":"9ed17bf5-1c87-4dba-b3a4-0f7195efa9e7","type":"Context","text":"I wait for 20 seconds"}],"tags":[],"astNodeIds":["d2e50951-bc48-4c28-b513-0e395ac0a146"]}} +,{"source":{"uri":"classpath:parallel/scenario-outlines.feature","data":"Feature: Scenario Outline\n\n Scenario Outline: Waiting\n\n Given I wait for seconds\n\n Examples:\n | seconds |\n | 5 |\n | 10 |\n | 15 |\n | 20 |","mediaType":"text/x.cucumber.gherkin+plain"}} +,{"gherkinDocument":{"uri":"classpath:parallel/scenario-outlines.feature","feature":{"location":{"line":1,"column":1},"tags":[],"language":"en","keyword":"Feature","name":"Scenario Outline","description":"","children":[{"scenario":{"location":{"line":3,"column":3},"tags":[],"keyword":"Scenario Outline","name":"Waiting","description":"","steps":[{"location":{"line":5,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for seconds","id":"9939e61a-a7ad-4164-b84a-73d97e0e1c0b"}],"examples":[{"location":{"line":7,"column":5},"tags":[],"keyword":"Examples","name":"","description":"","tableHeader":{"location":{"line":8,"column":7},"cells":[{"location":{"line":8,"column":9},"value":"seconds"}],"id":"4b96e4fe-035a-4781-9663-75312d646d6b"},"tableBody":[{"location":{"line":9,"column":7},"cells":[{"location":{"line":9,"column":9},"value":"5"}],"id":"4946302f-b4cf-4e73-9eed-634a9a099c55"},{"location":{"line":10,"column":7},"cells":[{"location":{"line":10,"column":9},"value":"10"}],"id":"a640ecbd-23b0-469d-bf24-4b72e45d5812"},{"location":{"line":11,"column":7},"cells":[{"location":{"line":11,"column":9},"value":"15"}],"id":"05a33b74-9aa0-412c-bcee-abcc26343512"},{"location":{"line":12,"column":7},"cells":[{"location":{"line":12,"column":9},"value":"20"}],"id":"3fbf7a14-5518-4518-bb90-0ac06dcf6aff"}],"id":"f250783f-e983-484d-af73-981bb08fed50"}],"id":"95e218da-cab5-4fbf-beea-6558b4747cdb"}}]},"comments":[]}} +,{"pickle":{"id":"5d6d82e8-a595-4263-844e-6f16accdc8df","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","4946302f-b4cf-4e73-9eed-634a9a099c55"],"id":"fb4414fa-e52d-4fbe-9b8c-eff368dc72b5","type":"Context","text":"I wait for 5 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","4946302f-b4cf-4e73-9eed-634a9a099c55"]}} +,{"pickle":{"id":"3986b982-1a48-4774-9ada-8a9e0349aa02","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","a640ecbd-23b0-469d-bf24-4b72e45d5812"],"id":"b0fecc18-4e3f-432f-8182-6449940ce593","type":"Context","text":"I wait for 10 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","a640ecbd-23b0-469d-bf24-4b72e45d5812"]}} +,{"pickle":{"id":"62d538a3-805a-4e77-aa84-2c4cdd23b78a","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","05a33b74-9aa0-412c-bcee-abcc26343512"],"id":"9961450c-9f4d-4fda-b0bf-e4990beade63","type":"Context","text":"I wait for 15 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","05a33b74-9aa0-412c-bcee-abcc26343512"]}} +,{"pickle":{"id":"8c7958a6-f6cb-4798-8f0f-7db60a263f1e","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","3fbf7a14-5518-4518-bb90-0ac06dcf6aff"],"id":"1f45b7e6-f3ab-435c-80bd-a8dee0219b76","type":"Context","text":"I wait for 20 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","3fbf7a14-5518-4518-bb90-0ac06dcf6aff"]}} +,{"stepDefinition":{"id":"122528c2-c6e9-49a1-ad3f-9cb365feaad0","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} +,{"stepDefinition":{"id":"9d239edf-87f2-4821-bfa0-bdff16aba193","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} +,{"stepDefinition":{"id":"c582c113-0461-4f5c-a1bd-db373063bfd4","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} +,{"stepDefinition":{"id":"0b938f0d-a79d-4d4a-90c5-51e72abb8160","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} +,{"testCase":{"id":"e191b6b7-a04b-4d30-a23b-58b05df68c89","pickleId":"ab4838f7-ff58-4ac6-885a-f9532b218146","testSteps":[{"id":"e164c9fb-06e3-49a5-8725-a9a8b2e9c28c","pickleStepId":"ae6e653e-d568-4c33-a99d-0653393bf03a","stepDefinitionIds":["122528c2-c6e9-49a1-ad3f-9cb365feaad0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"10"},"parameterTypeName":"int"}]}]}]}} +,{"testCase":{"id":"7c14d6c9-e1b0-4f90-8264-6c671b1f69c4","pickleId":"8c7958a6-f6cb-4798-8f0f-7db60a263f1e","testSteps":[{"id":"c301dac5-6567-41df-abe6-e78ccbb0d494","pickleStepId":"1f45b7e6-f3ab-435c-80bd-a8dee0219b76","stepDefinitionIds":["c582c113-0461-4f5c-a1bd-db373063bfd4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"20"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"f247c885-138a-414e-bcb4-f5a2e3dc053d","testCaseId":"7c14d6c9-e1b0-4f90-8264-6c671b1f69c4","workerId":"ForkJoinPool-2-worker-2","timestamp":{"seconds":1783364910,"nanos":655349506}}} +,{"testStepStarted":{"testCaseStartedId":"f247c885-138a-414e-bcb4-f5a2e3dc053d","testStepId":"c301dac5-6567-41df-abe6-e78ccbb0d494","timestamp":{"seconds":1783364910,"nanos":667969143}}} +,{"testCase":{"id":"6f9693a3-16a1-46ee-ba0e-62875dad40fe","pickleId":"90869e14-d1a1-46f8-b959-5e8d937772dd","testSteps":[{"id":"84506d2f-0582-4982-aad1-40a4d08b8987","pickleStepId":"9ed17bf5-1c87-4dba-b3a4-0f7195efa9e7","stepDefinitionIds":["9d239edf-87f2-4821-bfa0-bdff16aba193"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"20"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"6f571cd2-3514-41ad-aa35-1afdeb863439","testCaseId":"6f9693a3-16a1-46ee-ba0e-62875dad40fe","workerId":"ForkJoinPool-2-worker-1","timestamp":{"seconds":1783364910,"nanos":674377251}}} +,{"testStepStarted":{"testCaseStartedId":"6f571cd2-3514-41ad-aa35-1afdeb863439","testStepId":"84506d2f-0582-4982-aad1-40a4d08b8987","timestamp":{"seconds":1783364910,"nanos":676105417}}} +,{"testCase":{"id":"e5b2bec6-6f10-4e17-954c-5686cf8f8d63","pickleId":"8f92f4e6-d3a5-4db6-bfc7-ef4cd7ab7104","testSteps":[{"id":"d7dfbbeb-a408-414a-813f-f026b4a7fdd0","pickleStepId":"5f9baeb1-a1c0-4e58-9dbc-83e64836c83c","stepDefinitionIds":["0b938f0d-a79d-4d4a-90c5-51e72abb8160"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"5"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"0d58f229-68a7-471d-bdc2-4333844acf0b","testCaseId":"e5b2bec6-6f10-4e17-954c-5686cf8f8d63","workerId":"ForkJoinPool-2-worker-3","timestamp":{"seconds":1783364910,"nanos":677951850}}} +,{"testCaseStarted":{"attempt":0,"id":"f623c1cb-89a0-4a50-b279-341512fd4d09","testCaseId":"e191b6b7-a04b-4d30-a23b-58b05df68c89","workerId":"ForkJoinPool-2-worker-4","timestamp":{"seconds":1783364910,"nanos":653898230}}} +,{"testStepStarted":{"testCaseStartedId":"0d58f229-68a7-471d-bdc2-4333844acf0b","testStepId":"d7dfbbeb-a408-414a-813f-f026b4a7fdd0","timestamp":{"seconds":1783364910,"nanos":679224081}}} +,{"testStepStarted":{"testCaseStartedId":"f623c1cb-89a0-4a50-b279-341512fd4d09","testStepId":"e164c9fb-06e3-49a5-8725-a9a8b2e9c28c","timestamp":{"seconds":1783364910,"nanos":680352541}}} +,{"testStepFinished":{"testCaseStartedId":"0d58f229-68a7-471d-bdc2-4333844acf0b","testStepId":"d7dfbbeb-a408-414a-813f-f026b4a7fdd0","testStepResult":{"duration":{"seconds":5,"nanos":7209271},"status":"PASSED"},"timestamp":{"seconds":1783364915,"nanos":686433352}}} +,{"testCaseFinished":{"testCaseStartedId":"0d58f229-68a7-471d-bdc2-4333844acf0b","timestamp":{"seconds":1783364915,"nanos":694477859},"willBeRetried":false}} +,{"testCase":{"id":"714fa141-779b-44b5-9e1a-ac7eca676d2e","pickleId":"742927e4-043d-404c-aea3-83edf921a0aa","testSteps":[{"id":"6ee6ca02-77de-4d58-9a14-f5d1976c9409","pickleStepId":"a96d5267-582e-4857-9d96-6816dc3e4735","stepDefinitionIds":["0b938f0d-a79d-4d4a-90c5-51e72abb8160"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"15"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"58a3ca27-7b8a-4625-9441-67c937a02c44","testCaseId":"714fa141-779b-44b5-9e1a-ac7eca676d2e","workerId":"ForkJoinPool-2-worker-3","timestamp":{"seconds":1783364915,"nanos":704863171}}} +,{"testStepStarted":{"testCaseStartedId":"58a3ca27-7b8a-4625-9441-67c937a02c44","testStepId":"6ee6ca02-77de-4d58-9a14-f5d1976c9409","timestamp":{"seconds":1783364915,"nanos":705906636}}} +,{"testStepFinished":{"testCaseStartedId":"f623c1cb-89a0-4a50-b279-341512fd4d09","testStepId":"e164c9fb-06e3-49a5-8725-a9a8b2e9c28c","testStepResult":{"duration":{"seconds":10,"nanos":4022840},"status":"PASSED"},"timestamp":{"seconds":1783364920,"nanos":684375381}}} +,{"testCaseFinished":{"testCaseStartedId":"f623c1cb-89a0-4a50-b279-341512fd4d09","timestamp":{"seconds":1783364920,"nanos":685612826},"willBeRetried":false}} +,{"testCase":{"id":"709a4ecb-3b44-42de-a2dd-438c643f8cbf","pickleId":"5d6d82e8-a595-4263-844e-6f16accdc8df","testSteps":[{"id":"c023eb48-deee-4058-88de-7caf5ed11a1b","pickleStepId":"fb4414fa-e52d-4fbe-9b8c-eff368dc72b5","stepDefinitionIds":["122528c2-c6e9-49a1-ad3f-9cb365feaad0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"5"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"6f6868b3-d003-4d37-9485-b559f70cdfd5","testCaseId":"709a4ecb-3b44-42de-a2dd-438c643f8cbf","workerId":"ForkJoinPool-2-worker-4","timestamp":{"seconds":1783364920,"nanos":691156055}}} +,{"testStepStarted":{"testCaseStartedId":"6f6868b3-d003-4d37-9485-b559f70cdfd5","testStepId":"c023eb48-deee-4058-88de-7caf5ed11a1b","timestamp":{"seconds":1783364920,"nanos":692589594}}} +,{"testStepFinished":{"testCaseStartedId":"6f6868b3-d003-4d37-9485-b559f70cdfd5","testStepId":"c023eb48-deee-4058-88de-7caf5ed11a1b","testStepResult":{"duration":{"seconds":5,"nanos":1367400},"status":"PASSED"},"timestamp":{"seconds":1783364925,"nanos":693956994}}} +,{"testCaseFinished":{"testCaseStartedId":"6f6868b3-d003-4d37-9485-b559f70cdfd5","timestamp":{"seconds":1783364925,"nanos":695212572},"willBeRetried":false}} +,{"testCase":{"id":"53456c72-c9bc-48f6-9592-b8d78e5f14d3","pickleId":"3986b982-1a48-4774-9ada-8a9e0349aa02","testSteps":[{"id":"47d5e09f-7426-4e14-b7ee-ee78f3849810","pickleStepId":"b0fecc18-4e3f-432f-8182-6449940ce593","stepDefinitionIds":["122528c2-c6e9-49a1-ad3f-9cb365feaad0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"10"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"60372689-764d-41d5-bb81-8c82126437e9","testCaseId":"53456c72-c9bc-48f6-9592-b8d78e5f14d3","workerId":"ForkJoinPool-2-worker-4","timestamp":{"seconds":1783364925,"nanos":701163348}}} +,{"testStepStarted":{"testCaseStartedId":"60372689-764d-41d5-bb81-8c82126437e9","testStepId":"47d5e09f-7426-4e14-b7ee-ee78f3849810","timestamp":{"seconds":1783364925,"nanos":702147621}}} +,{"testStepFinished":{"testCaseStartedId":"6f571cd2-3514-41ad-aa35-1afdeb863439","testStepId":"84506d2f-0582-4982-aad1-40a4d08b8987","testStepResult":{"duration":{"seconds":20,"nanos":8562487},"status":"PASSED"},"timestamp":{"seconds":1783364930,"nanos":684667904}}} +,{"testCaseFinished":{"testCaseStartedId":"6f571cd2-3514-41ad-aa35-1afdeb863439","timestamp":{"seconds":1783364930,"nanos":685582964},"willBeRetried":false}} +,{"testStepFinished":{"testCaseStartedId":"f247c885-138a-414e-bcb4-f5a2e3dc053d","testStepId":"c301dac5-6567-41df-abe6-e78ccbb0d494","testStepResult":{"duration":{"seconds":20,"nanos":16704917},"status":"PASSED"},"timestamp":{"seconds":1783364930,"nanos":684674060}}} +,{"testCaseFinished":{"testCaseStartedId":"f247c885-138a-414e-bcb4-f5a2e3dc053d","timestamp":{"seconds":1783364930,"nanos":686985746},"willBeRetried":false}} +,{"testCase":{"id":"5430daee-cea6-4dfc-9daf-230654b96f14","pickleId":"62d538a3-805a-4e77-aa84-2c4cdd23b78a","testSteps":[{"id":"ee0f8afd-2bae-451a-99e6-cf597516abd0","pickleStepId":"9961450c-9f4d-4fda-b0bf-e4990beade63","stepDefinitionIds":["9d239edf-87f2-4821-bfa0-bdff16aba193"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"15"},"parameterTypeName":"int"}]}]}]}} +,{"testCaseStarted":{"attempt":0,"id":"46b79607-b297-44ed-afee-d91deecee9a0","testCaseId":"5430daee-cea6-4dfc-9daf-230654b96f14","workerId":"ForkJoinPool-2-worker-1","timestamp":{"seconds":1783364930,"nanos":690919185}}} +,{"testStepStarted":{"testCaseStartedId":"46b79607-b297-44ed-afee-d91deecee9a0","testStepId":"ee0f8afd-2bae-451a-99e6-cf597516abd0","timestamp":{"seconds":1783364930,"nanos":692260192}}} +,{"testStepFinished":{"testCaseStartedId":"58a3ca27-7b8a-4625-9441-67c937a02c44","testStepId":"6ee6ca02-77de-4d58-9a14-f5d1976c9409","testStepResult":{"duration":{"seconds":15,"nanos":951584},"status":"PASSED"},"timestamp":{"seconds":1783364930,"nanos":706858220}}} +,{"testCaseFinished":{"testCaseStartedId":"58a3ca27-7b8a-4625-9441-67c937a02c44","timestamp":{"seconds":1783364930,"nanos":708050850},"willBeRetried":false}} +,{"testStepFinished":{"testCaseStartedId":"60372689-764d-41d5-bb81-8c82126437e9","testStepId":"47d5e09f-7426-4e14-b7ee-ee78f3849810","testStepResult":{"duration":{"seconds":10,"nanos":1465881},"status":"PASSED"},"timestamp":{"seconds":1783364935,"nanos":703613502}}} +,{"testCaseFinished":{"testCaseStartedId":"60372689-764d-41d5-bb81-8c82126437e9","timestamp":{"seconds":1783364935,"nanos":704725162},"willBeRetried":false}} +,{"testStepFinished":{"testCaseStartedId":"46b79607-b297-44ed-afee-d91deecee9a0","testStepId":"ee0f8afd-2bae-451a-99e6-cf597516abd0","testStepResult":{"duration":{"seconds":15,"nanos":1091989},"status":"PASSED"},"timestamp":{"seconds":1783364945,"nanos":693352181}}} +,{"testCaseFinished":{"testCaseStartedId":"46b79607-b297-44ed-afee-d91deecee9a0","timestamp":{"seconds":1783364945,"nanos":694007340},"willBeRetried":false}} +,{"testRunFinished":{"success":true,"timestamp":{"seconds":1783364945,"nanos":699080137}}} +] as ReadonlyArray From 61a3d5003f9965fc67c786845801cbd9d9fc9130 Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Thu, 9 Jul 2026 16:15:31 +0500 Subject: [PATCH 8/9] removed old parallel-run file (locally generated) --- samples/parallel-run.ts | 63 ----------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 samples/parallel-run.ts diff --git a/samples/parallel-run.ts b/samples/parallel-run.ts deleted file mode 100644 index 7032c134..00000000 --- a/samples/parallel-run.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Envelope } from '@cucumber/messages' - -export default [ - {"meta":{"protocolVersion":"29.0.1","implementation":{"name":"cucumber-jvm","version":"7.30.0"},"runtime":{"name":"Java HotSpot(TM) 64-Bit Server VM","version":"26.0.1+8-34"},"os":{"name":"Linux"},"cpu":{"name":"amd64"}}} -,{"testRunStarted":{"timestamp":{"seconds":1783364910,"nanos":419492005}}} -,{"source":{"uri":"classpath:parallel/scenarios.feature","data":"Feature: Parallel Scenarios\n\n Scenario: One\n Given I wait for 5 seconds\n\n Scenario: Two\n Given I wait for 10 seconds\n\n Scenario: Three\n Given I wait for 15 seconds\n\n Scenario: Four\n Given I wait for 20 seconds","mediaType":"text/x.cucumber.gherkin+plain"}} -,{"gherkinDocument":{"uri":"classpath:parallel/scenarios.feature","feature":{"location":{"line":1,"column":1},"tags":[],"language":"en","keyword":"Feature","name":"Parallel Scenarios","description":"","children":[{"scenario":{"location":{"line":3,"column":3},"tags":[],"keyword":"Scenario","name":"One","description":"","steps":[{"location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 5 seconds","id":"ec7dd26c-eff5-4987-9e49-e4d6ebc47367"}],"examples":[],"id":"5ecdab5f-6f2e-4dbc-99bd-5d34087df0db"}},{"scenario":{"location":{"line":6,"column":3},"tags":[],"keyword":"Scenario","name":"Two","description":"","steps":[{"location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 10 seconds","id":"aad30de0-0f9d-4aee-aab6-4c9b89d2dc82"}],"examples":[],"id":"9ec6892a-20d6-4179-bc9b-0a5ee5352fcf"}},{"scenario":{"location":{"line":9,"column":3},"tags":[],"keyword":"Scenario","name":"Three","description":"","steps":[{"location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 15 seconds","id":"27fb1e9b-2da9-44df-9a40-3b9cd5f255dd"}],"examples":[],"id":"73fddbfe-bed6-4b96-b134-2e40bcbcbe77"}},{"scenario":{"location":{"line":12,"column":3},"tags":[],"keyword":"Scenario","name":"Four","description":"","steps":[{"location":{"line":13,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for 20 seconds","id":"23b79b90-7ae6-4614-8790-c5b01ed66d9e"}],"examples":[],"id":"d2e50951-bc48-4c28-b513-0e395ac0a146"}}]},"comments":[]}} -,{"pickle":{"id":"8f92f4e6-d3a5-4db6-bfc7-ef4cd7ab7104","uri":"classpath:parallel/scenarios.feature","name":"One","language":"en","steps":[{"astNodeIds":["ec7dd26c-eff5-4987-9e49-e4d6ebc47367"],"id":"5f9baeb1-a1c0-4e58-9dbc-83e64836c83c","type":"Context","text":"I wait for 5 seconds"}],"tags":[],"astNodeIds":["5ecdab5f-6f2e-4dbc-99bd-5d34087df0db"]}} -,{"pickle":{"id":"ab4838f7-ff58-4ac6-885a-f9532b218146","uri":"classpath:parallel/scenarios.feature","name":"Two","language":"en","steps":[{"astNodeIds":["aad30de0-0f9d-4aee-aab6-4c9b89d2dc82"],"id":"ae6e653e-d568-4c33-a99d-0653393bf03a","type":"Context","text":"I wait for 10 seconds"}],"tags":[],"astNodeIds":["9ec6892a-20d6-4179-bc9b-0a5ee5352fcf"]}} -,{"pickle":{"id":"742927e4-043d-404c-aea3-83edf921a0aa","uri":"classpath:parallel/scenarios.feature","name":"Three","language":"en","steps":[{"astNodeIds":["27fb1e9b-2da9-44df-9a40-3b9cd5f255dd"],"id":"a96d5267-582e-4857-9d96-6816dc3e4735","type":"Context","text":"I wait for 15 seconds"}],"tags":[],"astNodeIds":["73fddbfe-bed6-4b96-b134-2e40bcbcbe77"]}} -,{"pickle":{"id":"90869e14-d1a1-46f8-b959-5e8d937772dd","uri":"classpath:parallel/scenarios.feature","name":"Four","language":"en","steps":[{"astNodeIds":["23b79b90-7ae6-4614-8790-c5b01ed66d9e"],"id":"9ed17bf5-1c87-4dba-b3a4-0f7195efa9e7","type":"Context","text":"I wait for 20 seconds"}],"tags":[],"astNodeIds":["d2e50951-bc48-4c28-b513-0e395ac0a146"]}} -,{"source":{"uri":"classpath:parallel/scenario-outlines.feature","data":"Feature: Scenario Outline\n\n Scenario Outline: Waiting\n\n Given I wait for seconds\n\n Examples:\n | seconds |\n | 5 |\n | 10 |\n | 15 |\n | 20 |","mediaType":"text/x.cucumber.gherkin+plain"}} -,{"gherkinDocument":{"uri":"classpath:parallel/scenario-outlines.feature","feature":{"location":{"line":1,"column":1},"tags":[],"language":"en","keyword":"Feature","name":"Scenario Outline","description":"","children":[{"scenario":{"location":{"line":3,"column":3},"tags":[],"keyword":"Scenario Outline","name":"Waiting","description":"","steps":[{"location":{"line":5,"column":5},"keyword":"Given ","keywordType":"Context","text":"I wait for seconds","id":"9939e61a-a7ad-4164-b84a-73d97e0e1c0b"}],"examples":[{"location":{"line":7,"column":5},"tags":[],"keyword":"Examples","name":"","description":"","tableHeader":{"location":{"line":8,"column":7},"cells":[{"location":{"line":8,"column":9},"value":"seconds"}],"id":"4b96e4fe-035a-4781-9663-75312d646d6b"},"tableBody":[{"location":{"line":9,"column":7},"cells":[{"location":{"line":9,"column":9},"value":"5"}],"id":"4946302f-b4cf-4e73-9eed-634a9a099c55"},{"location":{"line":10,"column":7},"cells":[{"location":{"line":10,"column":9},"value":"10"}],"id":"a640ecbd-23b0-469d-bf24-4b72e45d5812"},{"location":{"line":11,"column":7},"cells":[{"location":{"line":11,"column":9},"value":"15"}],"id":"05a33b74-9aa0-412c-bcee-abcc26343512"},{"location":{"line":12,"column":7},"cells":[{"location":{"line":12,"column":9},"value":"20"}],"id":"3fbf7a14-5518-4518-bb90-0ac06dcf6aff"}],"id":"f250783f-e983-484d-af73-981bb08fed50"}],"id":"95e218da-cab5-4fbf-beea-6558b4747cdb"}}]},"comments":[]}} -,{"pickle":{"id":"5d6d82e8-a595-4263-844e-6f16accdc8df","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","4946302f-b4cf-4e73-9eed-634a9a099c55"],"id":"fb4414fa-e52d-4fbe-9b8c-eff368dc72b5","type":"Context","text":"I wait for 5 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","4946302f-b4cf-4e73-9eed-634a9a099c55"]}} -,{"pickle":{"id":"3986b982-1a48-4774-9ada-8a9e0349aa02","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","a640ecbd-23b0-469d-bf24-4b72e45d5812"],"id":"b0fecc18-4e3f-432f-8182-6449940ce593","type":"Context","text":"I wait for 10 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","a640ecbd-23b0-469d-bf24-4b72e45d5812"]}} -,{"pickle":{"id":"62d538a3-805a-4e77-aa84-2c4cdd23b78a","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","05a33b74-9aa0-412c-bcee-abcc26343512"],"id":"9961450c-9f4d-4fda-b0bf-e4990beade63","type":"Context","text":"I wait for 15 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","05a33b74-9aa0-412c-bcee-abcc26343512"]}} -,{"pickle":{"id":"8c7958a6-f6cb-4798-8f0f-7db60a263f1e","uri":"classpath:parallel/scenario-outlines.feature","name":"Waiting","language":"en","steps":[{"astNodeIds":["9939e61a-a7ad-4164-b84a-73d97e0e1c0b","3fbf7a14-5518-4518-bb90-0ac06dcf6aff"],"id":"1f45b7e6-f3ab-435c-80bd-a8dee0219b76","type":"Context","text":"I wait for 20 seconds"}],"tags":[],"astNodeIds":["95e218da-cab5-4fbf-beea-6558b4747cdb","3fbf7a14-5518-4518-bb90-0ac06dcf6aff"]}} -,{"stepDefinition":{"id":"122528c2-c6e9-49a1-ad3f-9cb365feaad0","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} -,{"stepDefinition":{"id":"9d239edf-87f2-4821-bfa0-bdff16aba193","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} -,{"stepDefinition":{"id":"c582c113-0461-4f5c-a1bd-db373063bfd4","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} -,{"stepDefinition":{"id":"0b938f0d-a79d-4d4a-90c5-51e72abb8160","pattern":{"source":"I wait for {int} seconds","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"javaMethod":{"className":"parallel.StepDefinitions","methodName":"waitFor","methodParameterTypes":["int"]}}}} -,{"testCase":{"id":"e191b6b7-a04b-4d30-a23b-58b05df68c89","pickleId":"ab4838f7-ff58-4ac6-885a-f9532b218146","testSteps":[{"id":"e164c9fb-06e3-49a5-8725-a9a8b2e9c28c","pickleStepId":"ae6e653e-d568-4c33-a99d-0653393bf03a","stepDefinitionIds":["122528c2-c6e9-49a1-ad3f-9cb365feaad0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"10"},"parameterTypeName":"int"}]}]}]}} -,{"testCase":{"id":"7c14d6c9-e1b0-4f90-8264-6c671b1f69c4","pickleId":"8c7958a6-f6cb-4798-8f0f-7db60a263f1e","testSteps":[{"id":"c301dac5-6567-41df-abe6-e78ccbb0d494","pickleStepId":"1f45b7e6-f3ab-435c-80bd-a8dee0219b76","stepDefinitionIds":["c582c113-0461-4f5c-a1bd-db373063bfd4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"20"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"f247c885-138a-414e-bcb4-f5a2e3dc053d","testCaseId":"7c14d6c9-e1b0-4f90-8264-6c671b1f69c4","workerId":"ForkJoinPool-2-worker-2","timestamp":{"seconds":1783364910,"nanos":655349506}}} -,{"testStepStarted":{"testCaseStartedId":"f247c885-138a-414e-bcb4-f5a2e3dc053d","testStepId":"c301dac5-6567-41df-abe6-e78ccbb0d494","timestamp":{"seconds":1783364910,"nanos":667969143}}} -,{"testCase":{"id":"6f9693a3-16a1-46ee-ba0e-62875dad40fe","pickleId":"90869e14-d1a1-46f8-b959-5e8d937772dd","testSteps":[{"id":"84506d2f-0582-4982-aad1-40a4d08b8987","pickleStepId":"9ed17bf5-1c87-4dba-b3a4-0f7195efa9e7","stepDefinitionIds":["9d239edf-87f2-4821-bfa0-bdff16aba193"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"20"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"6f571cd2-3514-41ad-aa35-1afdeb863439","testCaseId":"6f9693a3-16a1-46ee-ba0e-62875dad40fe","workerId":"ForkJoinPool-2-worker-1","timestamp":{"seconds":1783364910,"nanos":674377251}}} -,{"testStepStarted":{"testCaseStartedId":"6f571cd2-3514-41ad-aa35-1afdeb863439","testStepId":"84506d2f-0582-4982-aad1-40a4d08b8987","timestamp":{"seconds":1783364910,"nanos":676105417}}} -,{"testCase":{"id":"e5b2bec6-6f10-4e17-954c-5686cf8f8d63","pickleId":"8f92f4e6-d3a5-4db6-bfc7-ef4cd7ab7104","testSteps":[{"id":"d7dfbbeb-a408-414a-813f-f026b4a7fdd0","pickleStepId":"5f9baeb1-a1c0-4e58-9dbc-83e64836c83c","stepDefinitionIds":["0b938f0d-a79d-4d4a-90c5-51e72abb8160"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"5"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"0d58f229-68a7-471d-bdc2-4333844acf0b","testCaseId":"e5b2bec6-6f10-4e17-954c-5686cf8f8d63","workerId":"ForkJoinPool-2-worker-3","timestamp":{"seconds":1783364910,"nanos":677951850}}} -,{"testCaseStarted":{"attempt":0,"id":"f623c1cb-89a0-4a50-b279-341512fd4d09","testCaseId":"e191b6b7-a04b-4d30-a23b-58b05df68c89","workerId":"ForkJoinPool-2-worker-4","timestamp":{"seconds":1783364910,"nanos":653898230}}} -,{"testStepStarted":{"testCaseStartedId":"0d58f229-68a7-471d-bdc2-4333844acf0b","testStepId":"d7dfbbeb-a408-414a-813f-f026b4a7fdd0","timestamp":{"seconds":1783364910,"nanos":679224081}}} -,{"testStepStarted":{"testCaseStartedId":"f623c1cb-89a0-4a50-b279-341512fd4d09","testStepId":"e164c9fb-06e3-49a5-8725-a9a8b2e9c28c","timestamp":{"seconds":1783364910,"nanos":680352541}}} -,{"testStepFinished":{"testCaseStartedId":"0d58f229-68a7-471d-bdc2-4333844acf0b","testStepId":"d7dfbbeb-a408-414a-813f-f026b4a7fdd0","testStepResult":{"duration":{"seconds":5,"nanos":7209271},"status":"PASSED"},"timestamp":{"seconds":1783364915,"nanos":686433352}}} -,{"testCaseFinished":{"testCaseStartedId":"0d58f229-68a7-471d-bdc2-4333844acf0b","timestamp":{"seconds":1783364915,"nanos":694477859},"willBeRetried":false}} -,{"testCase":{"id":"714fa141-779b-44b5-9e1a-ac7eca676d2e","pickleId":"742927e4-043d-404c-aea3-83edf921a0aa","testSteps":[{"id":"6ee6ca02-77de-4d58-9a14-f5d1976c9409","pickleStepId":"a96d5267-582e-4857-9d96-6816dc3e4735","stepDefinitionIds":["0b938f0d-a79d-4d4a-90c5-51e72abb8160"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"15"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"58a3ca27-7b8a-4625-9441-67c937a02c44","testCaseId":"714fa141-779b-44b5-9e1a-ac7eca676d2e","workerId":"ForkJoinPool-2-worker-3","timestamp":{"seconds":1783364915,"nanos":704863171}}} -,{"testStepStarted":{"testCaseStartedId":"58a3ca27-7b8a-4625-9441-67c937a02c44","testStepId":"6ee6ca02-77de-4d58-9a14-f5d1976c9409","timestamp":{"seconds":1783364915,"nanos":705906636}}} -,{"testStepFinished":{"testCaseStartedId":"f623c1cb-89a0-4a50-b279-341512fd4d09","testStepId":"e164c9fb-06e3-49a5-8725-a9a8b2e9c28c","testStepResult":{"duration":{"seconds":10,"nanos":4022840},"status":"PASSED"},"timestamp":{"seconds":1783364920,"nanos":684375381}}} -,{"testCaseFinished":{"testCaseStartedId":"f623c1cb-89a0-4a50-b279-341512fd4d09","timestamp":{"seconds":1783364920,"nanos":685612826},"willBeRetried":false}} -,{"testCase":{"id":"709a4ecb-3b44-42de-a2dd-438c643f8cbf","pickleId":"5d6d82e8-a595-4263-844e-6f16accdc8df","testSteps":[{"id":"c023eb48-deee-4058-88de-7caf5ed11a1b","pickleStepId":"fb4414fa-e52d-4fbe-9b8c-eff368dc72b5","stepDefinitionIds":["122528c2-c6e9-49a1-ad3f-9cb365feaad0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"5"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"6f6868b3-d003-4d37-9485-b559f70cdfd5","testCaseId":"709a4ecb-3b44-42de-a2dd-438c643f8cbf","workerId":"ForkJoinPool-2-worker-4","timestamp":{"seconds":1783364920,"nanos":691156055}}} -,{"testStepStarted":{"testCaseStartedId":"6f6868b3-d003-4d37-9485-b559f70cdfd5","testStepId":"c023eb48-deee-4058-88de-7caf5ed11a1b","timestamp":{"seconds":1783364920,"nanos":692589594}}} -,{"testStepFinished":{"testCaseStartedId":"6f6868b3-d003-4d37-9485-b559f70cdfd5","testStepId":"c023eb48-deee-4058-88de-7caf5ed11a1b","testStepResult":{"duration":{"seconds":5,"nanos":1367400},"status":"PASSED"},"timestamp":{"seconds":1783364925,"nanos":693956994}}} -,{"testCaseFinished":{"testCaseStartedId":"6f6868b3-d003-4d37-9485-b559f70cdfd5","timestamp":{"seconds":1783364925,"nanos":695212572},"willBeRetried":false}} -,{"testCase":{"id":"53456c72-c9bc-48f6-9592-b8d78e5f14d3","pickleId":"3986b982-1a48-4774-9ada-8a9e0349aa02","testSteps":[{"id":"47d5e09f-7426-4e14-b7ee-ee78f3849810","pickleStepId":"b0fecc18-4e3f-432f-8182-6449940ce593","stepDefinitionIds":["122528c2-c6e9-49a1-ad3f-9cb365feaad0"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"10"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"60372689-764d-41d5-bb81-8c82126437e9","testCaseId":"53456c72-c9bc-48f6-9592-b8d78e5f14d3","workerId":"ForkJoinPool-2-worker-4","timestamp":{"seconds":1783364925,"nanos":701163348}}} -,{"testStepStarted":{"testCaseStartedId":"60372689-764d-41d5-bb81-8c82126437e9","testStepId":"47d5e09f-7426-4e14-b7ee-ee78f3849810","timestamp":{"seconds":1783364925,"nanos":702147621}}} -,{"testStepFinished":{"testCaseStartedId":"6f571cd2-3514-41ad-aa35-1afdeb863439","testStepId":"84506d2f-0582-4982-aad1-40a4d08b8987","testStepResult":{"duration":{"seconds":20,"nanos":8562487},"status":"PASSED"},"timestamp":{"seconds":1783364930,"nanos":684667904}}} -,{"testCaseFinished":{"testCaseStartedId":"6f571cd2-3514-41ad-aa35-1afdeb863439","timestamp":{"seconds":1783364930,"nanos":685582964},"willBeRetried":false}} -,{"testStepFinished":{"testCaseStartedId":"f247c885-138a-414e-bcb4-f5a2e3dc053d","testStepId":"c301dac5-6567-41df-abe6-e78ccbb0d494","testStepResult":{"duration":{"seconds":20,"nanos":16704917},"status":"PASSED"},"timestamp":{"seconds":1783364930,"nanos":684674060}}} -,{"testCaseFinished":{"testCaseStartedId":"f247c885-138a-414e-bcb4-f5a2e3dc053d","timestamp":{"seconds":1783364930,"nanos":686985746},"willBeRetried":false}} -,{"testCase":{"id":"5430daee-cea6-4dfc-9daf-230654b96f14","pickleId":"62d538a3-805a-4e77-aa84-2c4cdd23b78a","testSteps":[{"id":"ee0f8afd-2bae-451a-99e6-cf597516abd0","pickleStepId":"9961450c-9f4d-4fda-b0bf-e4990beade63","stepDefinitionIds":["9d239edf-87f2-4821-bfa0-bdff16aba193"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"start":11,"value":"15"},"parameterTypeName":"int"}]}]}]}} -,{"testCaseStarted":{"attempt":0,"id":"46b79607-b297-44ed-afee-d91deecee9a0","testCaseId":"5430daee-cea6-4dfc-9daf-230654b96f14","workerId":"ForkJoinPool-2-worker-1","timestamp":{"seconds":1783364930,"nanos":690919185}}} -,{"testStepStarted":{"testCaseStartedId":"46b79607-b297-44ed-afee-d91deecee9a0","testStepId":"ee0f8afd-2bae-451a-99e6-cf597516abd0","timestamp":{"seconds":1783364930,"nanos":692260192}}} -,{"testStepFinished":{"testCaseStartedId":"58a3ca27-7b8a-4625-9441-67c937a02c44","testStepId":"6ee6ca02-77de-4d58-9a14-f5d1976c9409","testStepResult":{"duration":{"seconds":15,"nanos":951584},"status":"PASSED"},"timestamp":{"seconds":1783364930,"nanos":706858220}}} -,{"testCaseFinished":{"testCaseStartedId":"58a3ca27-7b8a-4625-9441-67c937a02c44","timestamp":{"seconds":1783364930,"nanos":708050850},"willBeRetried":false}} -,{"testStepFinished":{"testCaseStartedId":"60372689-764d-41d5-bb81-8c82126437e9","testStepId":"47d5e09f-7426-4e14-b7ee-ee78f3849810","testStepResult":{"duration":{"seconds":10,"nanos":1465881},"status":"PASSED"},"timestamp":{"seconds":1783364935,"nanos":703613502}}} -,{"testCaseFinished":{"testCaseStartedId":"60372689-764d-41d5-bb81-8c82126437e9","timestamp":{"seconds":1783364935,"nanos":704725162},"willBeRetried":false}} -,{"testStepFinished":{"testCaseStartedId":"46b79607-b297-44ed-afee-d91deecee9a0","testStepId":"ee0f8afd-2bae-451a-99e6-cf597516abd0","testStepResult":{"duration":{"seconds":15,"nanos":1091989},"status":"PASSED"},"timestamp":{"seconds":1783364945,"nanos":693352181}}} -,{"testCaseFinished":{"testCaseStartedId":"46b79607-b297-44ed-afee-d91deecee9a0","timestamp":{"seconds":1783364945,"nanos":694007340},"willBeRetried":false}} -,{"testRunFinished":{"success":true,"timestamp":{"seconds":1783364945,"nanos":699080137}}} -] as ReadonlyArray From 9cb7ea261eacca8da7ff40f6464ce4aa0dfc2e4c Mon Sep 17 00:00:00 2001 From: Muhammad Talha Date: Thu, 9 Jul 2026 18:35:06 +0500 Subject: [PATCH 9/9] Reimplemented Timeline component using vis-timeline --- package-lock.json | 175 +++++++++++++++++++++--- package.json | 4 +- src/components/app/Timeline.module.scss | 116 ++-------------- src/components/app/Timeline.stories.tsx | 43 +----- src/components/app/Timeline.tsx | 154 ++++++++++----------- src/custom.d.ts | 2 + src/hooks/useTimelineData.ts | 27 ++-- 7 files changed, 271 insertions(+), 250 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10d6a7a1..fe74a55a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,9 @@ "rehype-sanitize": "6.0.0", "remark-breaks": "4.0.0", "remark-gfm": "4.0.1", - "use-debounce": "^10.0.0" + "use-debounce": "^10.0.0", + "vis-data": "^8.0.4", + "vis-timeline": "^8.5.1" }, "devDependencies": { "@biomejs/biome": "^2.4.1", @@ -924,6 +926,19 @@ "integrity": "sha512-uap3XSQFxj5HYAHQIShGeS2zotMEnUmnEVjyuhp39j7tDUvaU64ArHzRkLPSWRavEI8ycdeNdwef1pcM/n6pSQ==", "license": "MIT" }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", @@ -5266,6 +5281,13 @@ "glob": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/highlight-words-core": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/highlight-words-core/-/highlight-words-core-1.2.3.tgz", @@ -6227,6 +6249,16 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -6440,6 +6472,13 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/cssfilter": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", + "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==", + "license": "MIT", + "peer": true + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -8884,6 +8923,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/keycharm": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.4.0.tgz", + "integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==", + "license": "(Apache-2.0 OR MIT)", + "peer": true + }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -10891,6 +10937,16 @@ "node": ">=12" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11697,6 +11753,16 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/propagating-hammerjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-3.0.0.tgz", + "integrity": "sha512-FJTclGll0ysatpF9rKO4jwobyaVDitPb0g/bGlufqqtXPQX8mxf8IXilnIK2iYRMPkVlYeFNhPTrspF9CM1stg==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.17" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -13671,6 +13737,20 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/uuid": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -13687,6 +13767,59 @@ "node": ">= 0.8" } }, + "node_modules/vis-data": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/vis-data/-/vis-data-8.0.4.tgz", + "integrity": "sha512-TsN0sMHqIRpdfg6TNPtfdINpkgxtnQP6JNWCaiSwvou5seXqKiP5eERkaBg+Y56wyJ4FZTeOEs/dEmWEPrpltQ==", + "license": "(Apache-2.0 OR MIT)", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0 || ^14.0.0", + "vis-util": ">=6.0.0" + } + }, + "node_modules/vis-timeline": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/vis-timeline/-/vis-timeline-8.5.1.tgz", + "integrity": "sha512-6pqx4Zl/xHCEy5nXRaz9xCOx1HtZWkxSIt1oHJmDZy/UxT09kwq1eA4eKRAzhHey3PN1Ee3DsxuxHm7yI2G2mQ==", + "license": "(Apache-2.0 OR MIT)", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0", + "keycharm": "^0.2.0 || ^0.3.0 || ^0.4.0", + "moment": "^2.24.0", + "propagating-hammerjs": "^1.4.0 || ^2.0.0 || ^3.0.0", + "uuid": "^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0 || ^14.0.0", + "vis-data": ">=8.0.0", + "vis-util": ">=6.0.0", + "xss": "^1.0.0" + } + }, + "node_modules/vis-util": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vis-util/-/vis-util-6.0.0.tgz", + "integrity": "sha512-qtpts3HRma0zPe4bO7t9A2uejkRNj8Z2Tb6do6lN85iPNWExFkUiVhdAq5uLGIUqBFduyYeqWJKv/jMkxX0R5g==", + "license": "(Apache-2.0 OR MIT)", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/visjs" + }, + "peerDependencies": { + "@egjs/hammerjs": "^2.0.0", + "component-emitter": "^1.3.0 || ^2.0.0" + } + }, "node_modules/vite": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", @@ -13803,22 +13936,6 @@ } } }, - "node_modules/vite-tsconfig-paths/node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/vite/node_modules/fdir": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", @@ -14090,6 +14207,30 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xss": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", + "integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "commander": "^2.20.3", + "cssfilter": "0.0.10" + }, + "bin": { + "xss": "bin/xss" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/xss/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 5a273935..4dbd079a 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,9 @@ "rehype-sanitize": "6.0.0", "remark-breaks": "4.0.0", "remark-gfm": "4.0.1", - "use-debounce": "^10.0.0" + "use-debounce": "^10.0.0", + "vis-data": "^8.0.4", + "vis-timeline": "^8.5.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", diff --git a/src/components/app/Timeline.module.scss b/src/components/app/Timeline.module.scss index 3cb8622b..a8e4f64c 100644 --- a/src/components/app/Timeline.module.scss +++ b/src/components/app/Timeline.module.scss @@ -2,123 +2,25 @@ @use '../../styles/theming'; .container { - display: flex; - flex-direction: column; - gap: 1em; -} - -.empty { - font-style: italic; -} - -.chart { - position: relative; - overflow-x: auto; + border: 1px solid theming.$panelAccentColor; } -.axis { - position: relative; - height: 1.5em; - margin: 0 0 0.5em; - padding: 0; - list-style: none; - border-bottom: 1px solid theming.$panelAccentColor; - min-width: 40em; -} - -.tick { - position: absolute; - top: 0; - bottom: 0; - border-left: 1px dashed theming.$panelAccentColor; - padding-left: 0.35em; - font-size: 0.75em; - opacity: 0.75; - white-space: nowrap; - - &[data-edge='end'] { - border-left: none; - border-right: 1px dashed theming.$panelAccentColor; - padding-left: 0; - padding-right: 0.35em; - text-align: right; - - span { - display: inline-block; - transform: translateX(-100%); - } +@each $name, $color in statuses.$statusColors { + .visItem[data-status='#{$name}'] { + background-color: $color; + border-color: $color; } -} - -.groups { - display: flex; - flex-direction: column; - gap: 0.25em; - padding: 0; - margin: 0; - list-style: none; - min-width: 40em; -} -.group { - display: flex; - align-items: center; - gap: 0.5em; -} - -.groupLabel { - flex: 0 0 auto; - width: 8em; - font-size: 0.85em; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - opacity: 0.75; -} +.visItem[data-status='#{$name}']:global(.vis-selected) { + background-color: $color; + border-color: $color; -.lane { - position: relative; - flex: 1 1 auto; - height: 2.25em; - background-color: theming.$panelBackgroundColor; - border-radius: 0.25em; -} - -.item { - position: absolute; - top: 0.25em; - bottom: 0.25em; - min-width: 6px; - padding: 0 0.4em; - overflow: hidden; - border: none; - border-radius: 0.2em; - cursor: pointer; - color: white; - font: inherit; - font-size: 0.75em; - text-align: left; - - @each $name, $color in statuses.$statusColors { - &[data-status='#{$name}'] { - background-color: $color; - } - } - - &[aria-pressed='true'] { outline: 2px solid theming.$panelTextColor; outline-offset: 1px; - z-index: 1; + z-index: 2; } } -.itemLabel { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - .detail { position: relative; padding: 1em; diff --git a/src/components/app/Timeline.stories.tsx b/src/components/app/Timeline.stories.tsx index af99a13f..d746ce5d 100644 --- a/src/components/app/Timeline.stories.tsx +++ b/src/components/app/Timeline.stories.tsx @@ -1,7 +1,7 @@ -import { type Envelope, type TestCaseStarted, TimeConversion } from '@cucumber/messages' +import type { Envelope } from '@cucumber/messages' import type { Story } from '@ladle/react' - -import examplesTablesFeature from '../../../acceptance/examples-tables/examples-tables.js' +import examplesTables from '../../../acceptance/examples-tables/examples-tables.js' +import parallel from '../../../acceptance/parallel/parallel.js' import { EnvelopesProvider } from './EnvelopesProvider.js' import { InMemorySearchProvider } from './InMemorySearchProvider.js' import { Timeline } from './Timeline.js' @@ -26,46 +26,15 @@ const Template: Story = ({ envelopes }) => { export const SingleProcess = Template.bind({}) SingleProcess.args = { - envelopes: examplesTablesFeature, + envelopes: examplesTables, } as TemplateArgs export const Parallel = Template.bind({}) Parallel.args = { - envelopes: distributeAcrossWorkers(examplesTablesFeature, 3), + envelopes: parallel, } as TemplateArgs export const NoTestCases = Template.bind({}) NoTestCases.args = { - envelopes: [ - { testRunStarted: { timestamp: TimeConversion.millisecondsSinceEpochToTimestamp(0) } }, - { - testRunFinished: { - timestamp: TimeConversion.millisecondsSinceEpochToTimestamp(1000), - success: true, - }, - }, - ], + envelopes: [], } as TemplateArgs - -/** - * Cucumber implementations report which worker ran a test case via - * `TestCaseStarted.workerId`. The compatibility-kit fixtures used in this story - * were captured from a single-process run so this helper distributes the - * existing test cases across a number of synthetic workers to demonstrate how - * the timeline renders parallel execution. - */ -function distributeAcrossWorkers( - envelopes: ReadonlyArray, - workerCount: number -): ReadonlyArray { - let index = 0 - return envelopes.map((envelope): Envelope => { - if (!envelope.testCaseStarted) { - return envelope - } - const workerId = String(index % workerCount) - index += 1 - const testCaseStarted: TestCaseStarted = { ...envelope.testCaseStarted, workerId } - return { ...envelope, testCaseStarted } - }) -} diff --git a/src/components/app/Timeline.tsx b/src/components/app/Timeline.tsx index e121fc4b..9c501bbe 100644 --- a/src/components/app/Timeline.tsx +++ b/src/components/app/Timeline.tsx @@ -1,19 +1,84 @@ -import { faXmark } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { type FC, useState } from 'react' - +import { type FC, useEffect, useRef, useState } from 'react' +import { DataSet } from 'vis-data' +import { Timeline as VisTimeline } from 'vis-timeline' import { formatExecutionDuration } from '../../formatExecutionDuration.js' import { type TimelineItem, useTimelineData } from '../../hooks/useTimelineData.js' import { StatusIcon } from '../gherkin/StatusIcon.js' import statusName from '../gherkin/statusName.js' import { Tags } from '../gherkin/Tags.js' +import { TestCaseOutcome } from '../results/index.js' import styles from './Timeline.module.scss' -const AXIS_TICKS = 4 +import 'vis-timeline/styles/vis-timeline-graph2d.css' +import { faXmark } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +type DataSetGroup = { + id: string + content: string +} + +type DataSetItem = { + id: string + content: string + group: string + start: Date + end: Date + status: string + className: string +} export const Timeline: FC = () => { - const { groups, items, start, end, filtered } = useTimelineData() - const [selectedId, setSelectedId] = useState() + const { groups, items, fullStart, fullEnd, filtered } = useTimelineData() + const [selectedId, setSelectedId] = useState() + + const containerRef = useRef(null) + + useEffect(() => { + if (!containerRef.current) { + return + } + + const dataSetGroups = new DataSet() + groups.forEach((g) => { + dataSetGroups.add({ id: g.id, content: g.label }) + }) + + const dataSetItems = new DataSet() + items.forEach((i) => { + dataSetItems.add({ + id: i.id, + content: i.scenario, + group: i.groupId, + start: new Date(i.start), + end: new Date(i.end), + status: i.status, + className: styles.visItem, + }) + }) + + const timeline = new VisTimeline(containerRef.current, dataSetItems, dataSetGroups, { + stack: false, + zoomable: true, + moveable: true, + selectable: true, + editable: false, + showCurrentTime: false, + orientation: 'top', + min: fullStart, + max: fullEnd, + start: fullStart, + end: fullEnd, + dataAttributes: ['status'], + }) + + timeline.on('select', (props: { items: string[] }) => { + setSelectedId(props.items[0] ?? undefined) + }) + + return () => { + timeline.destroy() + } + }, [fullStart, fullEnd, groups, items]) if (items.length === 0) { return filtered ? ( @@ -23,56 +88,11 @@ export const Timeline: FC = () => { ) } - const duration = Math.max(end - start, 1) - const ticks = Array.from({ length: AXIS_TICKS + 1 }, (_, index) => { - const offset = (duration / AXIS_TICKS) * index - return { - index, - position: (offset / duration) * 100, - label: formatExecutionDuration(new Date(start), new Date(start + offset)), - } - }) const selectedItem = items.find((item) => item.id === selectedId) return ( -
-
- -
    - {groups.map((group) => ( -
  1. - {group.label} -
    - {items - .filter((item) => item.groupId === group.id) - .map((item) => ( - - setSelectedId((current) => (current === item.id ? undefined : item.id)) - } - /> - ))} -
    -
  2. - ))} -
-
+
+
{selectedItem && ( setSelectedId(undefined)} /> )} @@ -80,31 +100,6 @@ export const Timeline: FC = () => { ) } -const TimelineBar: FC<{ - item: TimelineItem - rangeStart: number - duration: number - selected: boolean - onSelect: () => void -}> = ({ item, rangeStart, duration, selected, onSelect }) => { - const left = ((item.start - rangeStart) / duration) * 100 - const width = Math.max(((item.end - item.start) / duration) * 100, 0.3) - return ( - - ) -} - const TimelineDetail: FC<{ item: TimelineItem; onClose: () => void }> = ({ item, onClose }) => { return (
@@ -131,6 +126,7 @@ const TimelineDetail: FC<{ item: TimelineItem; onClose: () => void }> = ({ item,
{item.groupLabel}
+
) } diff --git a/src/custom.d.ts b/src/custom.d.ts index e48f665b..14a684e2 100644 --- a/src/custom.d.ts +++ b/src/custom.d.ts @@ -2,3 +2,5 @@ declare module '*.module.scss' { const classes: { [key: string]: string } export default classes } + +declare module '*.css' diff --git a/src/hooks/useTimelineData.ts b/src/hooks/useTimelineData.ts index 0385a03d..16ef807e 100644 --- a/src/hooks/useTimelineData.ts +++ b/src/hooks/useTimelineData.ts @@ -30,8 +30,8 @@ export interface TimelineGroup { export interface TimelineData { readonly groups: readonly TimelineGroup[] readonly items: readonly TimelineItem[] - readonly start: number - readonly end: number + readonly fullStart: number | undefined + readonly fullEnd: number | undefined readonly filtered: boolean } @@ -45,6 +45,8 @@ export function useTimelineData(): TimelineData { const items: TimelineItem[] = [] const groupIds = new Set() const normalizedSearchTerm = searchTerm?.trim().toLowerCase() + let fullStart: number | undefined + let fullEnd: number | undefined for (const testCaseFinished of cucumberQuery.findAllTestCaseFinished()) { const testCaseStarted = cucumberQuery.findTestCaseStartedBy(testCaseFinished) @@ -56,6 +58,16 @@ export function useTimelineData(): TimelineData { continue } + const itemStart = TimeConversion.timestampToMillisecondsSinceEpoch(testCaseStarted.timestamp) + const itemEnd = TimeConversion.timestampToMillisecondsSinceEpoch(testCaseFinished.timestamp) + + if (fullStart === undefined || itemStart < fullStart) { + fullStart = itemStart + } + if (fullEnd === undefined || itemEnd > fullEnd) { + fullEnd = itemEnd + } + // A test case with no step results at all is considered passed by definition const status = cucumberQuery.findMostSevereTestStepResultBy(testCaseFinished)?.status ?? @@ -93,8 +105,8 @@ export function useTimelineData(): TimelineData { scenario, tags: pickle.tags, status, - start: TimeConversion.timestampToMillisecondsSinceEpoch(testCaseStarted.timestamp), - end: TimeConversion.timestampToMillisecondsSinceEpoch(testCaseFinished.timestamp), + start: itemStart, + end: itemEnd, testCaseStarted, }) } @@ -105,15 +117,12 @@ export function useTimelineData(): TimelineData { .sort(compareGroupIds) .map((id) => ({ id, label: describeGroup(id) })) - const start = items.length > 0 ? Math.min(...items.map((item) => item.start)) : 0 - const end = items.length > 0 ? Math.max(...items.map((item) => item.end)) : 0 - - return { groups, items, start, end, filtered: !unchanged } + return { groups, items, fullStart, fullEnd, filtered: !unchanged } }, [cucumberQuery, hideStatuses, tagExpression, searchTerm, unchanged]) } function describeGroup(id: string): string { - return id === UNASSIGNED_GROUP_ID ? 'Main process' : `Worker ${id}` + return id === UNASSIGNED_GROUP_ID ? '' : `Worker ${id}` } function compareGroupIds(a: string, b: string): number {