Dedupe dom helpers in timeline tests.

Bug: 380878140
Test: npm run test:unit:ci
Change-Id: Iab5c0f14bdf9a6132c185f65b31e93e5f4e2d0a6
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
index 6b87048..84ea67f 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/default_timeline_row_component_test.ts
@@ -15,7 +15,7 @@
  */
 
 import {DragDropModule} from '@angular/cdk/drag-drop';
-import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {TestBed} from '@angular/core/testing';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButtonModule} from '@angular/material/button';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -28,15 +28,15 @@
 import {Rect} from 'common/geometry/rect';
 import {TimestampConverterUtils} from 'common/time/test_utils';
 import {TimeRange} from 'common/time/time';
+import {DOMTestHelper} from 'test/unit/dom_test_utils';
 import {waitToBeCalled} from 'test/unit/spy_utils';
 import {TraceBuilder} from 'test/unit/trace_builder';
 import {TraceType} from 'trace/trace_type';
 import {DefaultTimelineRowComponent} from './default_timeline_row_component';
 
 describe('DefaultTimelineRowComponent', () => {
-  let fixture: ComponentFixture<DefaultTimelineRowComponent>;
   let component: DefaultTimelineRowComponent;
-  let htmlElement: HTMLElement;
+  let dom: DOMTestHelper<DefaultTimelineRowComponent>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -54,9 +54,9 @@
       ],
       declarations: [DefaultTimelineRowComponent],
     }).compileComponents();
-    fixture = TestBed.createComponent(DefaultTimelineRowComponent);
+    const fixture = TestBed.createComponent(DefaultTimelineRowComponent);
     component = fixture.componentInstance;
-    htmlElement = fixture.nativeElement;
+    dom = new DOMTestHelper(fixture, fixture.nativeElement);
   });
 
   it('can be created', () => {
@@ -65,14 +65,11 @@
 
   it('can draw entries', async () => {
     setTraceAndSelectionRange(10n, 110n);
-
     const drawRectSpy = spyOn(
       component.canvasDrawer,
       'drawRect',
     ).and.callThrough();
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const rectHeight = component.canvasDrawer.getScaledCanvasHeight();
     const rectWidth = rectHeight;
@@ -106,11 +103,8 @@
 
   it('can draw entries zoomed in', async () => {
     setTraceAndSelectionRange(60n, 85n);
-
     const drawRectSpy = spyOn(component.canvasDrawer, 'drawRect');
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const rectHeight = component.canvasDrawer.getScaledCanvasHeight();
     const rectWidth = rectHeight;
@@ -129,9 +123,7 @@
 
   it('can draw hovering entry', async () => {
     setTraceAndSelectionRange(10n, 110n);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const drawRectSpy = spyOn(
       component.canvasDrawer,
@@ -154,9 +146,7 @@
     );
     component.getCanvas().dispatchEvent(event);
 
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
-
+    await dom.detectChangesAndRenderingDone();
     await Promise.all(waitPromises);
 
     const rectHeight = component.canvasDrawer.getScaledCanvasHeight();
@@ -178,19 +168,14 @@
 
   it('can draw correct entry on click of first entry', async () => {
     setTraceAndSelectionRange(10n, 110n);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
-
+    await dom.detectChangesAndRenderingDone();
     // 9 rect draws - 4 entry rects present + 4 for redraw + 1 for selected entry
     await drawCorrectEntryOnClick(0, 10n, 9);
   });
 
   it('can draw correct entry on click of middle entry', async () => {
     setTraceAndSelectionRange(10n, 110n);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const canvasWidth = Math.floor(
       component.canvasDrawer.getScaledCanvasWidth() -
@@ -204,9 +189,7 @@
 
   it('can draw correct entry on click when timeline zoomed in near start', async () => {
     setTraceAndSelectionRange(10n, 15n);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const canvasWidth = Math.floor(
       component.canvasDrawer.getScaledCanvasWidth() -
@@ -220,9 +203,7 @@
 
   it('can draw correct entry on click when timeline zoomed in near end', async () => {
     setTraceAndSelectionRange(60n, 80n);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const canvasWidth = Math.floor(
       component.canvasDrawer.getScaledCanvasWidth() -
@@ -236,20 +217,15 @@
 
   it('emits scroll event', async () => {
     setTraceAndSelectionRange(10n, 110n);
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
-
+    await dom.detectChangesAndRenderingDone();
     const spy = spyOn(component.onScrollEvent, 'emit');
-    htmlElement.dispatchEvent(new WheelEvent('wheel'));
-    fixture.detectChanges();
+    dom.dispatchEvent(new WheelEvent('wheel'));
     expect(spy).toHaveBeenCalled();
   });
 
   it('tracks mouse position', async () => {
     setTraceAndSelectionRange(10n, 110n);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     const spy = spyOn(component.onMouseXRatioUpdate, 'emit');
     const canvas = assertDefined(component.canvasRef).nativeElement;
@@ -258,13 +234,13 @@
     Object.defineProperty(mouseMoveEvent, 'target', {value: canvas});
     Object.defineProperty(mouseMoveEvent, 'offsetX', {value: 100});
     canvas.dispatchEvent(mouseMoveEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     expect(spy).toHaveBeenCalledWith(100 / canvas.offsetWidth);
 
     const mouseLeaveEvent = new MouseEvent('mouseleave');
     canvas.dispatchEvent(mouseLeaveEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
     expect(spy).toHaveBeenCalledWith(undefined);
   });
 
@@ -305,9 +281,7 @@
       component.canvasDrawer.getScaledCanvasHeight() / 2,
     );
     component.getCanvas().dispatchEvent(event);
-
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     await Promise.all(waitPromises);
 
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts
index 8a59898..5b99659 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts
@@ -16,7 +16,7 @@
 
 import {DragDropModule} from '@angular/cdk/drag-drop';
 import {ChangeDetectionStrategy} from '@angular/core';
-import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {TestBed} from '@angular/core/testing';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButtonModule} from '@angular/material/button';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -28,6 +28,7 @@
 import {TimelineData} from 'app/timeline_data';
 import {assertDefined} from 'common/assert_utils';
 import {TimestampConverterUtils} from 'common/time/test_utils';
+import {DOMTestHelper} from 'test/unit/dom_test_utils';
 import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
 import {TracesBuilder} from 'test/unit/traces_builder';
 import {TracePosition} from 'trace/trace_position';
@@ -37,9 +38,8 @@
 import {TransitionTimelineComponent} from './transition_timeline_component';
 
 describe('ExpandedTimelineComponent', () => {
-  let fixture: ComponentFixture<ExpandedTimelineComponent>;
   let component: ExpandedTimelineComponent;
-  let htmlElement: HTMLElement;
+  let dom: DOMTestHelper<ExpandedTimelineComponent>;
   let timelineData: TimelineData;
   const time10 = TimestampConverterUtils.makeRealTimestamp(10n);
   const time11 = TimestampConverterUtils.makeRealTimestamp(11n);
@@ -72,9 +72,9 @@
         set: {changeDetection: ChangeDetectionStrategy.Default},
       })
       .compileComponents();
-    fixture = TestBed.createComponent(ExpandedTimelineComponent);
+    const fixture = TestBed.createComponent(ExpandedTimelineComponent);
     component = fixture.componentInstance;
-    htmlElement = fixture.nativeElement;
+    dom = new DOMTestHelper(fixture, fixture.nativeElement);
     timelineData = new TimelineData();
     const traces = new TracesBuilder()
       .setEntries(TraceType.SURFACE_FLINGER, [{}])
@@ -133,21 +133,17 @@
   });
 
   it('renders all timelines', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    const timelineElements = htmlElement.querySelectorAll(
-      '.timeline.row single-timeline',
-    );
+    const timelineElements = dom.findAll('.timeline.row single-timeline');
     expect(timelineElements.length).toEqual(4);
 
-    const transitionElement = htmlElement.querySelectorAll(
-      '.timeline.row transition-timeline',
-    );
+    const transitionElement = dom.findAll('.timeline.row transition-timeline');
     expect(transitionElement.length).toEqual(1);
   });
 
   it('passes initial selectedEntry of correct type into each timeline', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const singleTimelines = assertDefined(component.singleTimelines);
     expect(singleTimelines.length).toBe(4);
@@ -173,7 +169,7 @@
     assertDefined(component.timelineData).setPosition(
       TracePosition.fromTimestamp(time11),
     );
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const singleTimelines = assertDefined(component.singleTimelines);
     expect(singleTimelines.length).toBe(4);
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
index 39e4405..e77e7ce 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
@@ -16,7 +16,7 @@
 
 import {DragDropModule} from '@angular/cdk/drag-drop';
 import {ChangeDetectionStrategy} from '@angular/core';
-import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {TestBed} from '@angular/core/testing';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButtonModule} from '@angular/material/button';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -29,6 +29,7 @@
 import {Rect} from 'common/geometry/rect';
 import {TimestampConverterUtils} from 'common/time/test_utils';
 import {TimeRange, Timestamp} from 'common/time/time';
+import {DOMTestHelper} from 'test/unit/dom_test_utils';
 import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
 import {waitToBeCalled} from 'test/unit/spy_utils';
 import {TraceBuilder} from 'test/unit/trace_builder';
@@ -37,9 +38,8 @@
 import {TransitionTimelineComponent} from './transition_timeline_component';
 
 describe('TransitionTimelineComponent', () => {
-  let fixture: ComponentFixture<TransitionTimelineComponent>;
   let component: TransitionTimelineComponent;
-  let htmlElement: HTMLElement;
+  let dom: DOMTestHelper<TransitionTimelineComponent>;
 
   const time0 = TimestampConverterUtils.makeRealTimestamp(0n);
   const time5 = TimestampConverterUtils.makeRealTimestamp(5n);
@@ -76,9 +76,9 @@
         set: {changeDetection: ChangeDetectionStrategy.Default},
       })
       .compileComponents();
-    fixture = TestBed.createComponent(TransitionTimelineComponent);
+    const fixture = TestBed.createComponent(TransitionTimelineComponent);
     component = fixture.componentInstance;
-    htmlElement = fixture.nativeElement;
+    dom = new DOMTestHelper(fixture, fixture.nativeElement);
     component.timestampConverter = TimestampConverterUtils.TIMESTAMP_CONVERTER;
     component.fullRange = range0to160;
   });
@@ -227,14 +227,12 @@
 
     const mouseoutEvent = new MouseEvent('mouseout');
     component.getCanvas().dispatchEvent(mouseoutEvent);
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
     expect(drawRectSpy).not.toHaveBeenCalled();
 
     await dispatchMousemoveEvent();
     component.getCanvas().dispatchEvent(mouseoutEvent);
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
 
     expect(drawRectSpy).toHaveBeenCalledOnceWith(
       getExpectedBorderedRect(),
@@ -413,18 +411,14 @@
 
     const drawRectSpy = spyOn(component.canvasDrawer, 'drawRect');
 
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
-
+    await dom.detectChangesAndRenderingDone();
     expect(drawRectSpy).toHaveBeenCalledTimes(1);
   });
 
   it('emits scroll event', async () => {
     await setDefaultTraceAndSelectionRange();
-
     const spy = spyOn(component.onScrollEvent, 'emit');
-    htmlElement.dispatchEvent(new WheelEvent('wheel'));
-    fixture.detectChanges();
+    dom.dispatchEvent(new WheelEvent('wheel'));
     expect(spy).toHaveBeenCalled();
   });
 
@@ -438,13 +432,13 @@
     Object.defineProperty(mouseMoveEvent, 'target', {value: canvas});
     Object.defineProperty(mouseMoveEvent, 'offsetX', {value: 100});
     canvas.dispatchEvent(mouseMoveEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     expect(spy).toHaveBeenCalledWith(100 / canvas.offsetWidth);
 
     const mouseLeaveEvent = new MouseEvent('mouseleave');
     canvas.dispatchEvent(mouseLeaveEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
     expect(spy).toHaveBeenCalledWith(undefined);
   });
 
@@ -458,8 +452,7 @@
     component.transitionEntries = transitions;
     component.selectionRange = range10to110;
     if (setSelectedEntry) component.selectedEntry = component.trace.getEntry(0);
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
   }
 
   function makeTransition(
@@ -514,8 +507,7 @@
       .build();
     component.transitionEntries = transitions;
     component.selectionRange = range;
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
   }
 
   function getExpectedBorderedRect(): Rect {
@@ -538,7 +530,6 @@
     );
     spyOnProperty(mousemoveEvent, 'offsetY').and.returnValue(25 / 2);
     component.getCanvas().dispatchEvent(mousemoveEvent);
-    fixture.detectChanges();
-    await fixture.whenRenderingDone();
+    await dom.detectChangesAndRenderingDone();
   }
 });
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/mini_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/mini-timeline/mini_timeline_component_test.ts
index b50e689..15592c4 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/mini_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/mini_timeline_component_test.ts
@@ -17,7 +17,7 @@
 import {DragDropModule} from '@angular/cdk/drag-drop';
 import {CdkMenuModule} from '@angular/cdk/menu';
 import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core';
-import {ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing';
+import {fakeAsync, TestBed} from '@angular/core/testing';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButtonModule} from '@angular/material/button';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -28,10 +28,11 @@
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 import {TimelineData} from 'app/timeline_data';
 import {assertDefined} from 'common/assert_utils';
+import {KeyboardEventCode} from 'common/dom_utils';
 import {TimestampConverterUtils} from 'common/time/test_utils';
 import {TimeRange, Timestamp} from 'common/time/time';
+import {DOMTestHelper} from 'test/unit/dom_test_utils';
 import {TracesBuilder} from 'test/unit/traces_builder';
-import {dragElement} from 'test/utils';
 import {Trace} from 'trace/trace';
 import {TracePosition} from 'trace/trace_position';
 import {TraceType} from 'trace/trace_type';
@@ -39,11 +40,15 @@
 import {SliderComponent} from './slider_component';
 
 describe('MiniTimelineComponent', () => {
-  let fixture: ComponentFixture<TestHostComponent>;
   let component: TestHostComponent;
-  let htmlElement: HTMLElement;
+  let dom: DOMTestHelper<TestHostComponent>;
   let timelineData: TimelineData;
 
+  const resetButtonSelector = 'button#reset-zoom-btn';
+  const zoomInSelector = '#zoom-in-btn';
+  const zoomOutSelector = '#zoom-out-btn';
+  const zoomControlSelector = '.zoom-control';
+
   const timestamp10 = TimestampConverterUtils.makeRealTimestamp(10n);
   const timestamp15 = TimestampConverterUtils.makeRealTimestamp(15n);
   const timestamp16 = TimestampConverterUtils.makeRealTimestamp(16n);
@@ -92,9 +97,9 @@
         set: {changeDetection: ChangeDetectionStrategy.Default},
       })
       .compileComponents();
-    fixture = TestBed.createComponent(TestHostComponent);
+    const fixture = TestBed.createComponent(TestHostComponent);
     component = fixture.componentInstance;
-    htmlElement = fixture.nativeElement;
+    dom = new DOMTestHelper(fixture, fixture.nativeElement);
 
     timelineData = new TimelineData();
     await timelineData.initialize(
@@ -113,7 +118,7 @@
   });
 
   it('redraws on resize', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     const miniTimelineComponent = assertDefined(
       component.miniTimelineComponent,
     );
@@ -121,7 +126,7 @@
     expect(spy).not.toHaveBeenCalled();
 
     window.dispatchEvent(new Event('resize'));
-    fixture.detectChanges();
+    dom.detectChanges();
 
     expect(spy).toHaveBeenCalled();
   });
@@ -133,61 +138,49 @@
     const zoomRange = timelineData.getZoomRange();
     expect(zoomRange).toEqual(expectedZoomRange);
     expect(zoomRange).not.toEqual(fullRange);
-    fixture.detectChanges();
-
-    getResetButton().click();
-    fixture.detectChanges();
-
+    dom.detectChanges();
+    dom.findAndClick(resetButtonSelector);
     expect(timelineData.getZoomRange()).toEqual(fullRange);
   });
 
   it('resets zoom to initial zoom on reset button click if available', () => {
     const initialZoom = new TimeRange(timestamp15, timestamp16);
     component.initialZoom = initialZoom;
-    fixture.detectChanges();
+    dom.detectChanges();
     expect(timelineData.getZoomRange()).toEqual(initialZoom);
 
     const newZoom = new TimeRange(timestamp10, timestamp16);
     timelineData.setZoom(newZoom);
     expect(timelineData.getZoomRange()).toEqual(newZoom);
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    getResetButton().click();
-    fixture.detectChanges();
-
+    dom.findAndClick(resetButtonSelector);
     expect(timelineData.getZoomRange()).toEqual(initialZoom);
     expect(timelineData.getFullTimeRange()).not.toEqual(initialZoom);
   });
 
   it('show zoom controls when zoomed out', () => {
-    const zoomControlDiv = assertDefined(
-      htmlElement.querySelector('.zoom-control'),
-    );
+    const zoomControlDiv = dom.get(zoomControlSelector).getHTMLElement();
     expect(window.getComputedStyle(zoomControlDiv).visibility).toBe('visible');
-
-    const zoomButton = getResetButton();
+    const zoomButton = dom.get(resetButtonSelector).getHTMLElement();
     expect(window.getComputedStyle(zoomButton).visibility).toBe('visible');
   });
 
   it('shows zoom controls when zoomed in', () => {
     const zoom = new TimeRange(timestamp15, timestamp16);
     timelineData.setZoom(zoom);
+    dom.detectChanges();
 
-    fixture.detectChanges();
-
-    const zoomControlDiv = assertDefined(
-      htmlElement.querySelector('.zoom-control'),
-    );
+    const zoomControlDiv = dom.get(zoomControlSelector).getHTMLElement();
     expect(window.getComputedStyle(zoomControlDiv).visibility).toBe('visible');
-
-    const zoomButton = getResetButton();
+    const zoomButton = dom.get(resetButtonSelector).getHTMLElement();
     expect(window.getComputedStyle(zoomButton).visibility).toBe('visible');
   });
 
   it('loads with initial zoom', () => {
     const initialZoom = new TimeRange(timestamp15, timestamp16);
     component.initialZoom = initialZoom;
-    fixture.detectChanges();
+    dom.detectChanges();
     const timelineData = assertDefined(component.timelineData);
     const zoomRange = timelineData.getZoomRange();
     expect(zoomRange.from).toEqual(initialZoom.from);
@@ -195,22 +188,22 @@
   });
 
   it('updates timelineData on zoom changed', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     const zoom = new TimeRange(timestamp15, timestamp16);
     assertDefined(component.miniTimelineComponent).onZoomChanged(zoom);
-    fixture.detectChanges();
+    dom.detectChanges();
     expect(timelineData.getZoomRange()).toBe(zoom);
   });
 
   it('creates an appropriately sized canvas', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     const canvas = assertDefined(component.miniTimelineComponent).getCanvas();
     expect(canvas.width).toBeGreaterThan(100);
     expect(canvas.height).toBeGreaterThan(10);
   });
 
   it('getTracesToShow returns traces targeted by selectedTraces', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     const selectedTraces = assertDefined(component.selectedTraces);
     const selectedTracesTypes = selectedTraces.map((trace) => trace.type);
 
@@ -224,7 +217,7 @@
 
   it('getTracesToShow adds traces in correct order', () => {
     component.selectedTraces = [traceWm, traceSf, traceTransactions];
-    fixture.detectChanges();
+    dom.detectChanges();
     const tracesToShowTypes = assertDefined(component.miniTimelineComponent)
       .getTracesToShow()
       .map((trace) => trace.type);
@@ -236,16 +229,16 @@
   });
 
   it('updates zoom when slider moved', fakeAsync(() => {
-    fixture.detectChanges();
+    dom.detectChanges();
     const initialZoom = new TimeRange(timestamp15, timestamp16);
     assertDefined(component.miniTimelineComponent).onZoomChanged(initialZoom);
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    const slider = assertDefined(htmlElement.querySelector('.slider .handle'));
-    expect(window.getComputedStyle(slider).visibility).toEqual('visible');
+    const slider = dom.get('.slider .handle');
+    const sliderEl = slider.getHTMLElement();
+    expect(window.getComputedStyle(sliderEl).visibility).toEqual('visible');
 
-    dragElement(fixture, slider, 100, 8);
-
+    slider.dragElement(100, 8);
     const finalZoom = timelineData.getZoomRange();
     expect(finalZoom).not.toBe(initialZoom);
   }));
@@ -259,16 +252,13 @@
     );
     miniTimelineComponent.onZoomChanged(initialZoom);
     miniTimelineComponent.currentTracePosition = position800;
+    dom.detectChanges();
 
-    fixture.detectChanges();
-
-    getZoomInButton().click();
-    fixture.detectChanges();
+    dom.findAndClick(zoomInSelector);
     const zoomedIn = timelineData.getZoomRange();
     checkZoomDifference(initialZoom, zoomedIn);
 
-    getZoomOutButton().click();
-    fixture.detectChanges();
+    dom.findAndClick(zoomOutSelector);
     const zoomedOut = timelineData.getZoomRange();
     checkZoomDifference(zoomedOut, zoomedIn);
   });
@@ -280,17 +270,14 @@
     assertDefined(component.miniTimelineComponent).onZoomChanged(initialZoom);
 
     timelineData.setPosition(position800);
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    getZoomOutButton().click();
-    fixture.detectChanges();
-
+    dom.findAndClick(zoomOutSelector);
     let finalZoom = timelineData.getZoomRange();
     expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
     expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
 
     zoomOutByScrollWheel();
-
     finalZoom = timelineData.getZoomRange();
     expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
     expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
@@ -304,12 +291,10 @@
       component.miniTimelineComponent,
     );
     miniTimelineComponent.onZoomChanged(initialZoom);
-
-    fixture.detectChanges();
+    dom.detectChanges();
 
     for (let i = 0; i < 10; i++) {
       zoomInByScrollWheel();
-
       const finalZoom = timelineData.getZoomRange();
       checkZoomDifference(initialZoom, finalZoom);
       initialZoom = finalZoom;
@@ -317,7 +302,6 @@
 
     for (let i = 0; i < 9; i++) {
       zoomOutByScrollWheel();
-
       const finalZoom = timelineData.getZoomRange();
       checkZoomDifference(finalZoom, initialZoom);
       initialZoom = finalZoom;
@@ -329,7 +313,7 @@
 
     const initialZoom = new TimeRange(timestamp10, timestamp1000);
     assertDefined(component.miniTimelineComponent).onZoomChanged(initialZoom);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     component.expandedTimelineScrollEvent = {
       deltaY: -200,
@@ -337,41 +321,33 @@
       x: 10, // scrolling on pos
       target: component.miniTimelineComponent?.getCanvas(),
     } as unknown as WheelEvent;
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const finalZoom = timelineData.getZoomRange();
     checkZoomDifference(initialZoom, finalZoom);
   });
 
   it('opens context menu', () => {
-    fixture.detectChanges();
-    expect(document.querySelector('.context-menu')).toBeFalsy();
+    dom.detectChanges();
+    expect(dom.findInDocument('.context-menu')).toBeUndefined();
 
-    assertDefined(component.miniTimelineComponent)
-      .getCanvas()
-      .dispatchEvent(new MouseEvent('contextmenu'));
-    fixture.detectChanges();
-
+    openContextMenu(assertDefined(component.miniTimelineComponent));
     const options = getContextMenuItems();
     expect(options.length).toEqual(2);
   });
 
   it('adds bookmark', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     const miniTimelineComponent = assertDefined(
       component.miniTimelineComponent,
     );
     const spy = spyOn(miniTimelineComponent.onToggleBookmark, 'emit');
 
-    miniTimelineComponent
-      .getCanvas()
-      .dispatchEvent(new MouseEvent('contextmenu'));
-    fixture.detectChanges();
-
+    openContextMenu(miniTimelineComponent);
     const options = getContextMenuItems();
-    expect(options[0].textContent).toContain('Add bookmark');
-    options[0].click();
+    options[0].checkText('Add bookmark');
 
+    options[0].click();
     expect(spy).toHaveBeenCalledWith({
       range: new TimeRange(timestamp10, timestamp10),
       rangeContainsBookmark: false,
@@ -380,21 +356,16 @@
 
   it('removes bookmark', () => {
     component.bookmarks = [timestamp10];
-    fixture.detectChanges();
+    dom.detectChanges();
     const miniTimelineComponent = assertDefined(
       component.miniTimelineComponent,
     );
     const spy = spyOn(miniTimelineComponent.onToggleBookmark, 'emit');
 
-    miniTimelineComponent
-      .getCanvas()
-      .dispatchEvent(new MouseEvent('contextmenu'));
-    fixture.detectChanges();
-
+    openContextMenu(assertDefined(component.miniTimelineComponent));
     const options = getContextMenuItems();
-    expect(options[0].textContent).toContain('Remove bookmark');
+    options[0].checkText('Remove bookmark');
     options[0].click();
-
     expect(spy).toHaveBeenCalledWith({
       range: new TimeRange(timestamp10, timestamp10),
       rangeContainsBookmark: true,
@@ -403,21 +374,17 @@
 
   it('removes all bookmarks', () => {
     component.bookmarks = [timestamp10, timestamp1000];
-    fixture.detectChanges();
+    dom.detectChanges();
     const miniTimelineComponent = assertDefined(
       component.miniTimelineComponent,
     );
     const spy = spyOn(miniTimelineComponent.onRemoveAllBookmarks, 'emit');
 
-    miniTimelineComponent
-      .getCanvas()
-      .dispatchEvent(new MouseEvent('contextmenu'));
-    fixture.detectChanges();
-
+    openContextMenu(miniTimelineComponent);
     const options = getContextMenuItems();
-    expect(options[1].textContent).toContain('Remove all bookmarks');
-    options[1].click();
+    options[1].checkText('Remove all bookmarks');
 
+    options[1].click();
     expect(spy).toHaveBeenCalled();
   });
 
@@ -426,7 +393,7 @@
 
     const initialZoom = new TimeRange(timestamp1000, timestamp2000);
     component.initialZoom = initialZoom;
-    fixture.detectChanges();
+    dom.detectChanges();
 
     zoomInByKeyW();
     const zoomedIn = timelineData.getZoomRange();
@@ -442,11 +409,12 @@
 
     const initialZoom = new TimeRange(timestamp1000, timestamp2000);
     component.initialZoom = initialZoom;
-    fixture.detectChanges();
+    dom.detectChanges();
 
     while (timelineData.getZoomRange().to !== timestamp4000) {
-      document.dispatchEvent(new KeyboardEvent('keydown', {code: 'KeyD'}));
-      fixture.detectChanges();
+      dom.dispatchEventInDocument(
+        new KeyboardEvent('keydown', {code: KeyboardEventCode.D}),
+      );
       const zoomRange = timelineData.getZoomRange();
       const increase =
         zoomRange.from.getValueNs() - initialZoom.from.getValueNs();
@@ -458,13 +426,15 @@
 
     // cannot move past end of trace
     const finalZoom = timelineData.getZoomRange();
-    document.dispatchEvent(new KeyboardEvent('keydown', {code: 'KeyD'}));
-    fixture.detectChanges();
+    dom.dispatchEventInDocument(
+      new KeyboardEvent('keydown', {code: KeyboardEventCode.D}),
+    );
     expect(timelineData.getZoomRange()).toEqual(finalZoom);
 
     while (timelineData.getZoomRange().from !== timestamp1000) {
-      document.dispatchEvent(new KeyboardEvent('keydown', {code: 'KeyA'}));
-      fixture.detectChanges();
+      dom.dispatchEventInDocument(
+        new KeyboardEvent('keydown', {code: KeyboardEventCode.A}),
+      );
       const zoomRange = timelineData.getZoomRange();
       const decrease =
         finalZoom.from.getValueNs() - zoomRange.from.getValueNs();
@@ -475,8 +445,9 @@
     }
 
     // cannot move before start of trace
-    document.dispatchEvent(new KeyboardEvent('keydown', {code: 'KeyA'}));
-    fixture.detectChanges();
+    dom.dispatchEventInDocument(
+      new KeyboardEvent('keydown', {code: KeyboardEventCode.A}),
+    );
     expect(timelineData.getZoomRange()).toEqual(initialZoom);
   });
 
@@ -485,7 +456,7 @@
     const initialZoom = new TimeRange(timestamp1000, timestamp4000);
     component.initialZoom = initialZoom;
     component.currentTracePosition = TracePosition.fromTimestamp(timestamp2000);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const miniTimelineComponent = assertDefined(
       component.miniTimelineComponent,
@@ -501,7 +472,7 @@
         (usableRange.to - usableRange.from) * 0.25 + drawer.getPadding().left,
     });
     canvas.dispatchEvent(mouseMoveEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const fullRangeQuarterTimestamp = timestamp1750;
     checkZoomOnTimestamp(
@@ -525,7 +496,7 @@
     const initialZoom = new TimeRange(timestamp1000, timestamp4000);
     component.initialZoom = initialZoom;
     component.currentTracePosition = TracePosition.fromTimestamp(timestamp1750);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const fullRangeQuarterTimestamp = timestamp1750;
     checkZoomOnTimestamp(
@@ -543,20 +514,17 @@
       zoomOutByScrollWheel,
     );
 
-    const zoomInButton = getZoomInButton();
-    const zoomOutButton = getZoomOutButton();
-
+    const zoomInButton = dom.get(zoomInSelector);
+    const zoomOutButton = dom.get(zoomOutSelector);
     checkZoomOnTimestamp(
       fullRangeQuarterTimestamp,
       1n,
       4n,
       () => {
         zoomInButton.click();
-        fixture.detectChanges();
       },
       () => {
         zoomOutButton.click();
-        fixture.detectChanges();
       },
     );
   });
@@ -566,7 +534,7 @@
     const initialZoom = new TimeRange(timestamp1000, timestamp4000);
     component.initialZoom = initialZoom;
     component.currentTracePosition = TracePosition.fromTimestamp(timestamp1750);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const miniTimelineComponent = assertDefined(
       component.miniTimelineComponent,
@@ -581,9 +549,9 @@
       value: (usableRange.to - usableRange.from) * 0.5,
     });
     canvas.dispatchEvent(mouseMoveEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
     canvas.dispatchEvent(new MouseEvent('mouseleave'));
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const fullRangeQuarterTimestamp = timestamp1750;
     checkZoomOnTimestamp(
@@ -601,20 +569,17 @@
       zoomOutByScrollWheel,
     );
 
-    const zoomInButton = getZoomInButton();
-    const zoomOutButton = getZoomOutButton();
-
+    const zoomInButton = dom.get(zoomInSelector);
+    const zoomOutButton = dom.get(zoomOutSelector);
     checkZoomOnTimestamp(
       fullRangeQuarterTimestamp,
       1n,
       4n,
       () => {
         zoomInButton.click();
-        fixture.detectChanges();
       },
       () => {
         zoomOutButton.click();
-        fixture.detectChanges();
       },
     );
   });
@@ -624,7 +589,7 @@
     const initialZoom = new TimeRange(timestamp2000, timestamp4000);
     component.initialZoom = initialZoom;
     component.currentTracePosition = TracePosition.fromTimestamp(timestamp1750);
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const fullRangeMiddleTimestamp = timestamp3000;
     checkZoomOnTimestamp(
@@ -642,20 +607,17 @@
       zoomOutByScrollWheel,
     );
 
-    const zoomInButton = getZoomInButton();
-    const zoomOutButton = getZoomOutButton();
-
+    const zoomInButton = dom.get(zoomInSelector);
+    const zoomOutButton = dom.get(zoomOutSelector);
     checkZoomOnTimestamp(
       fullRangeMiddleTimestamp,
       1n,
       2n,
       () => {
         zoomInButton.click();
-        fixture.detectChanges();
       },
       () => {
         zoomOutButton.click();
-        fixture.detectChanges();
       },
     );
   });
@@ -664,10 +626,10 @@
     initializeTracesForWASDZoom();
     const initialZoom = new TimeRange(timestamp1000, timestamp4000);
     component.initialZoom = initialZoom;
-    fixture.detectChanges();
+    dom.detectChanges();
     component.currentTracePosition = TracePosition.fromTimestamp(timestamp2000);
     component.expandedTimelineMouseXRatio = 0.25;
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const fullRangeQuarterTimestamp = timestamp1750;
     checkZoomOnTimestamp(
@@ -698,7 +660,7 @@
       undefined,
       TimestampConverterUtils.TIMESTAMP_CONVERTER,
     );
-    fixture.detectChanges();
+    dom.detectChanges();
   }
 
   function initializeTracesForWASDZoom() {
@@ -731,13 +693,15 @@
   }
 
   function zoomInByKeyW() {
-    document.dispatchEvent(new KeyboardEvent('keydown', {code: 'KeyW'}));
-    fixture.detectChanges();
+    dom.dispatchEventInDocument(
+      new KeyboardEvent('keydown', {code: KeyboardEventCode.W}),
+    );
   }
 
   function zoomOutByKeyS() {
-    document.dispatchEvent(new KeyboardEvent('keydown', {code: 'KeyS'}));
-    fixture.detectChanges();
+    dom.dispatchEventInDocument(
+      new KeyboardEvent('keydown', {code: KeyboardEventCode.S}),
+    );
   }
 
   function zoomInByScrollWheel() {
@@ -747,7 +711,7 @@
       x: 10, // scrolling on pos
       target: {id: 'mini-timeline-canvas', offsetLeft: 0},
     } as unknown as WheelEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
   }
 
   function zoomOutByScrollWheel() {
@@ -757,30 +721,11 @@
       x: 10, // scrolling on pos
       target: {id: 'mini-timeline-canvas', offsetLeft: 0},
     } as unknown as WheelEvent);
-    fixture.detectChanges();
+    dom.detectChanges();
   }
 
-  function getResetButton(): HTMLElement {
-    return assertDefined(
-      htmlElement.querySelector<HTMLElement>('button#reset-zoom-btn'),
-    );
-  }
-
-  function getZoomInButton(): HTMLElement {
-    return assertDefined(
-      htmlElement.querySelector<HTMLElement>('#zoom-in-btn'),
-    );
-  }
-
-  function getZoomOutButton(): HTMLElement {
-    return assertDefined(
-      htmlElement.querySelector<HTMLElement>('#zoom-out-btn'),
-    );
-  }
-
-  function getContextMenuItems(): HTMLElement[] {
-    const menu = assertDefined(document.querySelector('.context-menu'));
-    return Array.from(menu.querySelectorAll<HTMLElement>('.context-menu-item'));
+  function getContextMenuItems(): Array<DOMTestHelper<TestHostComponent>> {
+    return dom.getInDocument('.context-menu')?.findAll('.context-menu-item');
   }
 
   function checkZoomOnTimestamp(
@@ -827,6 +772,13 @@
     }
   }
 
+  function openContextMenu(miniTimelineComponent: MiniTimelineComponent) {
+    miniTimelineComponent
+      .getCanvas()
+      .dispatchEvent(new MouseEvent('contextmenu'));
+    dom.detectChanges();
+  }
+
   @Component({
     selector: 'host-component',
     template: `
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/slider_component_test.ts b/tools/winscope/src/app/components/timeline/mini-timeline/slider_component_test.ts
index fd656fa..805d0e5 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/slider_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/slider_component_test.ts
@@ -16,7 +16,7 @@
 
 import {DragDropModule} from '@angular/cdk/drag-drop';
 import {ChangeDetectionStrategy} from '@angular/core';
-import {ComponentFixture, fakeAsync, TestBed} from '@angular/core/testing';
+import {fakeAsync, TestBed} from '@angular/core/testing';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButtonModule} from '@angular/material/button';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -28,14 +28,15 @@
 import {assertDefined} from 'common/assert_utils';
 import {TimestampConverterUtils} from 'common/time/test_utils';
 import {TimeRange} from 'common/time/time';
-import {dragElement} from 'test/utils';
+import {DOMTestHelper} from 'test/unit/dom_test_utils';
 import {TracePosition} from 'trace/trace_position';
 import {MIN_SLIDER_WIDTH, SliderComponent} from './slider_component';
 
 describe('SliderComponent', () => {
-  let fixture: ComponentFixture<SliderComponent>;
   let component: SliderComponent;
-  let htmlElement: HTMLElement;
+  let dom: DOMTestHelper<SliderComponent>;
+  const leftCropperSelector = '.slider .cropper.left';
+  const rightCropperSelector = '.slider .cropper.right';
   const time100 = TimestampConverterUtils.makeRealTimestamp(100n);
   const time125 = TimestampConverterUtils.makeRealTimestamp(125n);
   const time126 = TimestampConverterUtils.makeRealTimestamp(126n);
@@ -63,16 +64,14 @@
         set: {changeDetection: ChangeDetectionStrategy.Default},
       })
       .compileComponents();
-    fixture = TestBed.createComponent(SliderComponent);
+    const fixture = TestBed.createComponent(SliderComponent);
     component = fixture.componentInstance;
-    htmlElement = fixture.nativeElement;
-
+    dom = new DOMTestHelper(fixture, fixture.nativeElement);
     component.fullRange = new TimeRange(time100, time200);
     component.zoomRange = new TimeRange(time125, time175);
     component.currentPosition = TracePosition.fromTimestamp(time150);
     component.timestampConverter = TimestampConverterUtils.TIMESTAMP_CONVERTER;
-
-    fixture.detectChanges();
+    dom.detectChanges();
   });
 
   it('can be created', () => {
@@ -80,7 +79,7 @@
   });
 
   it('reposition properly on zoom', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     component.ngOnChanges({
       zoomRange: {
         firstChange: true,
@@ -89,18 +88,18 @@
         currentValue: component.zoomRange,
       },
     });
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    const sliderWitdth = component.sliderBox.nativeElement.offsetWidth;
-    expect(component.sliderWidth).toBe(sliderWitdth / 2);
-    expect(component.dragPosition.x).toBe(sliderWitdth / 4);
+    const sliderWidth = component.sliderBox.nativeElement.offsetWidth;
+    expect(component.sliderWidth).toEqual(sliderWidth / 2);
+    expect(component.dragPosition.x).toEqual(sliderWidth / 4);
   });
 
   it('has min width', () => {
     component.fullRange = new TimeRange(time100, time200);
     component.zoomRange = new TimeRange(time125, time126);
 
-    fixture.detectChanges();
+    dom.detectChanges();
     component.ngOnChanges({
       zoomRange: {
         firstChange: true,
@@ -109,21 +108,19 @@
         currentValue: component.zoomRange,
       },
     });
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const sliderWidth = component.sliderBox.nativeElement.offsetWidth;
-    expect(component.sliderWidth).toBe(MIN_SLIDER_WIDTH);
-    expect(component.dragPosition.x).toBe(
+    expect(component.sliderWidth).toEqual(MIN_SLIDER_WIDTH);
+    expect(component.dragPosition.x).toEqual(
       sliderWidth / 4 - MIN_SLIDER_WIDTH / 2,
     );
   });
 
   it('repositions slider on resize', () => {
-    const slider = assertDefined(htmlElement.querySelector('.slider'));
-    const cursor = assertDefined(htmlElement.querySelector('.cursor'));
-
-    fixture.detectChanges();
-
+    const slider = dom.get('.slider').getHTMLElement();
+    const cursor = dom.get('.cursor').getHTMLElement();
+    dom.detectChanges();
     const initialSliderXPos = slider.getBoundingClientRect().left;
     const initialCursorXPos = cursor.getBoundingClientRect().left;
 
@@ -132,18 +129,17 @@
       'offsetWidth',
       'get',
     ).and.returnValue(100);
-    expect(component.sliderBox.nativeElement.offsetWidth).toBe(100);
+    expect(component.sliderBox.nativeElement.offsetWidth).toEqual(100);
 
-    htmlElement.style.width = '587px';
+    slider.style.width = '587px';
     window.dispatchEvent(new Event('resize'));
-    fixture.detectChanges();
-
-    expect(initialSliderXPos).not.toBe(slider.getBoundingClientRect().left);
-    expect(initialCursorXPos).not.toBe(cursor.getBoundingClientRect().left);
+    dom.detectChanges();
+    expect(initialSliderXPos).not.toEqual(slider.getBoundingClientRect().left);
+    expect(initialCursorXPos).not.toEqual(cursor.getBoundingClientRect().left);
   });
 
   it('draws current position cursor', () => {
-    fixture.detectChanges();
+    dom.detectChanges();
     component.ngOnChanges({
       currentPosition: {
         firstChange: true,
@@ -152,12 +148,10 @@
         currentValue: component.currentPosition,
       },
     });
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    const sliderBox = assertDefined(
-      htmlElement.querySelector('#timeline-slider-box'),
-    );
-    const cursor = assertDefined(htmlElement.querySelector('.cursor'));
+    const sliderBox = dom.get('#timeline-slider-box').getHTMLElement();
+    const cursor = dom.get('.cursor').getHTMLElement();
     const sliderBoxRect = sliderBox.getBoundingClientRect();
     expect(cursor.getBoundingClientRect().left).toBeCloseTo(
       (sliderBoxRect.left + sliderBoxRect.right) / 2,
@@ -166,8 +160,7 @@
   });
 
   it('moving slider around updates zoom', fakeAsync(async () => {
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -177,27 +170,21 @@
       },
     );
 
-    const slider = htmlElement.querySelector('.slider .handle');
-    expect(slider).toBeTruthy();
-    expect(window.getComputedStyle(assertDefined(slider)).visibility).toBe(
-      'visible',
-    );
+    const slider = dom.get('.slider .handle');
+    checkVisible(slider.getHTMLElement());
 
-    dragElement(fixture, assertDefined(slider), 100, 8);
-
+    slider.dragElement(100, 8);
     expect(zoomChangedSpy).toHaveBeenCalled();
-
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
-    expect(finalZoom.from).not.toBe(initialZoom.from);
-    expect(finalZoom.to).not.toBe(initialZoom.to);
-    expect(finalZoom.to.minus(finalZoom.from.getValueNs()).getValueNs()).toBe(
-      initialZoom.to.minus(initialZoom.from.getValueNs()).getValueNs(),
-    );
+    expect(finalZoom.from).not.toEqual(initialZoom.from);
+    expect(finalZoom.to).not.toEqual(initialZoom.to);
+    expect(
+      finalZoom.to.minus(finalZoom.from.getValueNs()).getValueNs(),
+    ).toEqual(initialZoom.to.minus(initialZoom.from.getValueNs()).getValueNs());
   }));
 
   it('moving slider left pointer around updates zoom', fakeAsync(async () => {
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -207,14 +194,10 @@
       },
     );
 
-    const leftCropper = htmlElement.querySelector('.slider .cropper.left');
-    expect(leftCropper).toBeTruthy();
-    expect(window.getComputedStyle(assertDefined(leftCropper)).visibility).toBe(
-      'visible',
-    );
+    const leftCropper = dom.get(leftCropperSelector);
+    checkVisible(leftCropper.getHTMLElement());
 
-    dragElement(fixture, assertDefined(leftCropper), 5, 0);
-
+    leftCropper.dragElement(5, 0);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
@@ -223,8 +206,7 @@
   }));
 
   it('moving slider right pointer around updates zoom', fakeAsync(async () => {
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -234,14 +216,10 @@
       },
     );
 
-    const rightCropper = htmlElement.querySelector('.slider .cropper.right');
-    expect(rightCropper).toBeTruthy();
-    expect(
-      window.getComputedStyle(assertDefined(rightCropper)).visibility,
-    ).toBe('visible');
+    const rightCropper = dom.get(rightCropperSelector);
+    checkVisible(rightCropper.getHTMLElement());
 
-    dragElement(fixture, assertDefined(rightCropper), 5, 0);
-
+    rightCropper.dragElement(5, 0);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
@@ -251,8 +229,7 @@
 
   it('cannot slide left cropper past edges', fakeAsync(() => {
     component.zoomRange = component.fullRange;
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -262,25 +239,20 @@
       },
     );
 
-    const leftCropper = htmlElement.querySelector('.slider .cropper.left');
-    expect(leftCropper).toBeTruthy();
-    expect(window.getComputedStyle(assertDefined(leftCropper)).visibility).toBe(
-      'visible',
-    );
+    const leftCropper = dom.get(leftCropperSelector);
+    checkVisible(leftCropper.getHTMLElement());
 
-    dragElement(fixture, assertDefined(leftCropper), -5, 0);
-
+    leftCropper.dragElement(-5, 0);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
-    expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
-    expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
+    expect(finalZoom.from.getValueNs()).toEqual(initialZoom.from.getValueNs());
+    expect(finalZoom.to.getValueNs()).toEqual(initialZoom.to.getValueNs());
   }));
 
   it('cannot slide right cropper past edges', fakeAsync(() => {
     component.zoomRange = component.fullRange;
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -290,25 +262,20 @@
       },
     );
 
-    const rightCropper = htmlElement.querySelector('.slider .cropper.right');
-    expect(rightCropper).toBeTruthy();
-    expect(
-      window.getComputedStyle(assertDefined(rightCropper)).visibility,
-    ).toBe('visible');
+    const rightCropper = dom.get(rightCropperSelector);
+    checkVisible(rightCropper.getHTMLElement());
 
-    dragElement(fixture, assertDefined(rightCropper), 5, 0);
-
+    rightCropper.dragElement(5, 0);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
-    expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
-    expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
+    expect(finalZoom.from.getValueNs()).toEqual(initialZoom.from.getValueNs());
+    expect(finalZoom.to.getValueNs()).toEqual(initialZoom.to.getValueNs());
   }));
 
   it('cannot slide left cropper past right cropper', fakeAsync(() => {
     component.zoomRange = new TimeRange(time125, time125);
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -318,25 +285,20 @@
       },
     );
 
-    const leftCropper = htmlElement.querySelector('.slider .cropper.left');
-    expect(leftCropper).toBeTruthy();
-    expect(window.getComputedStyle(assertDefined(leftCropper)).visibility).toBe(
-      'visible',
-    );
+    const leftCropper = dom.get(leftCropperSelector);
+    checkVisible(leftCropper.getHTMLElement());
 
-    dragElement(fixture, assertDefined(leftCropper), 100, 0);
-
+    leftCropper.dragElement(100, 0);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
-    expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
-    expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
+    expect(finalZoom.from.getValueNs()).toEqual(initialZoom.from.getValueNs());
+    expect(finalZoom.to.getValueNs()).toEqual(initialZoom.to.getValueNs());
   }));
 
   it('cannot slide right cropper past left cropper', fakeAsync(() => {
     component.zoomRange = new TimeRange(time125, time125);
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -346,25 +308,20 @@
       },
     );
 
-    const rightCropper = htmlElement.querySelector('.slider .cropper.right');
-    expect(rightCropper).toBeTruthy();
-    expect(
-      window.getComputedStyle(assertDefined(rightCropper)).visibility,
-    ).toBe('visible');
+    const rightCropper = dom.get(rightCropperSelector);
+    checkVisible(rightCropper.getHTMLElement());
 
-    dragElement(fixture, assertDefined(rightCropper), -100, 0);
-
+    rightCropper.dragElement(-100, 0);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
-    expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
-    expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
+    expect(finalZoom.from.getValueNs()).toEqual(initialZoom.from.getValueNs());
+    expect(finalZoom.to.getValueNs()).toEqual(initialZoom.to.getValueNs());
   }));
 
   it('cannot move slider past edges', fakeAsync(() => {
     component.zoomRange = component.fullRange;
-    fixture.detectChanges();
-
+    dom.detectChanges();
     const initialZoom = assertDefined(component.zoomRange);
 
     let lastZoomUpdate: TimeRange | undefined = undefined;
@@ -374,18 +331,18 @@
       },
     );
 
-    const slider = htmlElement.querySelector('.slider .handle');
-    expect(slider).toBeTruthy();
-    expect(window.getComputedStyle(assertDefined(slider)).visibility).toBe(
-      'visible',
-    );
+    const slider = dom.get('.slider .handle');
+    checkVisible(slider.getHTMLElement());
 
-    dragElement(fixture, assertDefined(slider), 100, 8);
-
+    slider.dragElement(100, 8);
     expect(zoomChangedSpy).toHaveBeenCalled();
 
     const finalZoom = assertDefined<TimeRange>(lastZoomUpdate);
-    expect(finalZoom.from.getValueNs()).toBe(initialZoom.from.getValueNs());
-    expect(finalZoom.to.getValueNs()).toBe(initialZoom.to.getValueNs());
+    expect(finalZoom.from.getValueNs()).toEqual(initialZoom.from.getValueNs());
+    expect(finalZoom.to.getValueNs()).toEqual(initialZoom.to.getValueNs());
   }));
+
+  function checkVisible(element: HTMLElement) {
+    expect(window.getComputedStyle(element).visibility).toEqual('visible');
+  }
 });
diff --git a/tools/winscope/src/app/components/timeline/timeline_component_test.ts b/tools/winscope/src/app/components/timeline/timeline_component_test.ts
index 3558900..792591d 100644
--- a/tools/winscope/src/app/components/timeline/timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/timeline_component_test.ts
@@ -18,7 +18,7 @@
 import {DragDropModule} from '@angular/cdk/drag-drop';
 import {CdkMenuModule} from '@angular/cdk/menu';
 import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core';
-import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {TestBed} from '@angular/core/testing';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {MatButtonModule} from '@angular/material/button';
 import {MatFormFieldModule} from '@angular/material/form-field';
@@ -26,7 +26,6 @@
 import {MatInputModule} from '@angular/material/input';
 import {MatSelectModule} from '@angular/material/select';
 import {MatTooltipModule} from '@angular/material/tooltip';
-import {By} from '@angular/platform-browser';
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 import {
   MatDrawer,
@@ -50,6 +49,7 @@
   TraceSearchRequest,
   WinscopeEvent,
 } from 'messaging/winscope_event';
+import {checkTooltips, DOMTestHelper} from 'test/unit/dom_test_utils';
 import {TracesBuilder} from 'test/unit/traces_builder';
 import {TraceBuilder} from 'test/unit/trace_builder';
 import {UnitTestUtils} from 'test/unit/utils';
@@ -89,9 +89,11 @@
   const position110 = TracePosition.fromTimestamp(time110);
   const position112 = TracePosition.fromTimestamp(time112);
 
-  let fixture: ComponentFixture<TestHostComponent>;
+  const nextEntrySelector = '#next_entry_button';
+  const prevEntrySelector = '#prev_entry_button';
+
   let component: TestHostComponent;
-  let htmlElement: HTMLElement;
+  let dom: DOMTestHelper<TestHostComponent>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -126,9 +128,9 @@
         set: {changeDetection: ChangeDetectionStrategy.Default},
       })
       .compileComponents();
-    fixture = TestBed.createComponent(TestHostComponent);
+    const fixture = TestBed.createComponent(TestHostComponent);
     component = fixture.componentInstance;
-    htmlElement = fixture.nativeElement;
+    dom = new DOMTestHelper(fixture, fixture.nativeElement);
   });
 
   it('can be created', () => {
@@ -144,19 +146,15 @@
       undefined,
       TimestampConverterUtils.TIMESTAMP_CONVERTER,
     );
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const timelineComponent = assertDefined(component.timeline);
 
-    const button = assertDefined(
-      htmlElement.querySelector(`.${timelineComponent.TOGGLE_BUTTON_CLASS}`),
-    );
-
     // initially not expanded
-    let expandedTimelineElement = fixture.debugElement.query(
-      By.directive(ExpandedTimelineComponent),
+    let expandedTimelineElement = dom.findByDirective(
+      ExpandedTimelineComponent,
     );
-    expect(expandedTimelineElement).toBeFalsy();
+    expect(expandedTimelineElement).toBeUndefined();
 
     let isExpanded = false;
     timelineComponent.setEmitEvent(async (event: WinscopeEvent) => {
@@ -164,18 +162,16 @@
       isExpanded = (event as ExpandedTimelineToggled).isTimelineExpanded;
     });
 
-    button.dispatchEvent(new Event('click'));
-    expandedTimelineElement = fixture.debugElement.query(
-      By.directive(ExpandedTimelineComponent),
+    const button = dom.findAndClick(
+      `.${timelineComponent.TOGGLE_BUTTON_CLASS}`,
     );
-    expect(expandedTimelineElement).toBeTruthy();
+    expandedTimelineElement = dom.findByDirective(ExpandedTimelineComponent);
+    expect(expandedTimelineElement).toBeDefined();
     expect(isExpanded).toBeTrue();
 
-    button.dispatchEvent(new Event('click'));
-    expandedTimelineElement = fixture.debugElement.query(
-      By.directive(ExpandedTimelineComponent),
-    );
-    expect(expandedTimelineElement).toBeFalsy();
+    button.click();
+    expandedTimelineElement = dom.findByDirective(ExpandedTimelineComponent);
+    expect(expandedTimelineElement).toBeUndefined();
     expect(isExpanded).toBeFalse();
   });
 
@@ -188,18 +184,14 @@
       undefined,
       TimestampConverterUtils.TIMESTAMP_CONVERTER,
     );
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    expect(htmlElement.querySelector('.time-selector')).toBeNull();
-    expect(htmlElement.querySelector('.trace-selector')).toBeNull();
+    expect(dom.find('.time-selector')).toBeUndefined();
+    expect(dom.find('.trace-selector')).toBeUndefined();
 
-    const errorMessageContainer = assertDefined(
-      htmlElement.querySelector('.no-timeline-msg'),
-    );
-    expect(errorMessageContainer.textContent).toContain('No timeline to show!');
-    expect(errorMessageContainer.textContent).toContain(
-      'All loaded traces contain no timestamps.',
-    );
+    const errorMessageContainer = dom.get('.no-timeline-msg');
+    errorMessageContainer.checkText('No timeline to show!');
+    errorMessageContainer.checkText('All loaded traces contain no timestamps.');
 
     checkNoTimelineNavigation();
   });
@@ -207,18 +199,14 @@
   it('handles some empty traces and some with one timestamp', async () => {
     await loadTracesWithOneTimestamp();
 
-    expect(htmlElement.querySelector('#time-selector')).toBeTruthy();
-    const shownSelection = assertDefined(
-      htmlElement.querySelector('#trace-selector .shown-selection'),
-    );
-    expect(shownSelection.innerHTML).toContain('Window Manager');
-    expect(shownSelection.innerHTML).not.toContain('Surface Flinger');
+    expect(dom.find('#time-selector')).toBeDefined();
+    const shownSelection = dom.get('#trace-selector .shown-selection');
+    shownSelection.checkInnerHTML('Window Manager');
+    shownSelection.checkInnerHTML('Surface Flinger', false);
 
-    const errorMessageContainer = assertDefined(
-      htmlElement.querySelector('.no-timeline-msg'),
-    );
-    expect(errorMessageContainer.textContent).toContain('No timeline to show!');
-    expect(errorMessageContainer.textContent).toContain(
+    const errorMessageContainer = dom.get('.no-timeline-msg');
+    errorMessageContainer.checkText('No timeline to show!');
+    errorMessageContainer.checkText(
       'Only a single timestamp has been recorded.',
     );
 
@@ -227,20 +215,16 @@
 
   it('processes active trace input and updates selected traces', async () => {
     loadAllTraces();
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const timelineComponent = assertDefined(component.timeline);
-    const nextEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#next_entry_button'),
-    );
-    const prevEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#prev_entry_button'),
-    );
+    const nextEntryButton = dom.get(nextEntrySelector);
+    const prevEntryButton = dom.get(prevEntrySelector);
 
     timelineComponent.selectedTraces = [
       getLoadedTrace(TraceType.SURFACE_FLINGER),
     ];
-    fixture.detectChanges();
+    dom.detectChanges();
     checkActiveTraceSurfaceFlinger(nextEntryButton, prevEntryButton);
 
     // setting same trace as active does not affect selected traces
@@ -297,40 +281,32 @@
       TimestampConverterUtils.TIMESTAMP_CONVERTER,
     );
     timelineData.setPosition(position100);
-    fixture.detectChanges();
-    const nextEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#next_entry_button'),
-    );
-    const prevEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#prev_entry_button'),
-    );
+    dom.detectChanges();
+    const nextEntryButton = dom.get(nextEntrySelector);
+    const prevEntryButton = dom.get(prevEntrySelector);
     expect(timelineData.getActiveTrace()).toBeUndefined();
     expect(timelineData.getCurrentPosition()?.timestamp.getValueNs()).toEqual(
       100n,
     );
 
-    expect(prevEntryButton.getAttribute('disabled')).toEqual('true');
-    expect(nextEntryButton.getAttribute('disabled')).toEqual('true');
+    prevEntryButton.checkDisabled(true);
+    nextEntryButton.checkDisabled(true);
   });
 
   it('handles ActiveTraceChanged event', async () => {
     loadSfWmTraces();
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const timelineComponent = assertDefined(component.timeline);
-    const nextEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#next_entry_button'),
-    );
-    const prevEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#prev_entry_button'),
-    );
+    const nextEntryButton = dom.get(nextEntrySelector);
+    const prevEntryButton = dom.get(prevEntrySelector);
     const spy = spyOn(
       assertDefined(timelineComponent.miniTimeline?.drawer),
       'draw',
     );
 
     await updateActiveTrace(TraceType.SURFACE_FLINGER);
-    fixture.detectChanges();
+    dom.detectChanges();
     checkActiveTraceSurfaceFlinger(nextEntryButton, prevEntryButton);
     expect(spy).toHaveBeenCalled();
   });
@@ -355,32 +331,26 @@
     await component.timeline?.onWinscopeEvent(new TraceAddRequest(searchTrace));
     expectSelectedTraceTypes(allTraceTypes);
 
-    await openSelectPanel();
+    await dom.openMatSelect();
 
-    const matOptions =
-      document.documentElement.querySelectorAll<HTMLInputElement>('mat-option');
-    await UnitTestUtils.checkTooltips(
-      Array.from(matOptions),
-      [
-        'test query, 0',
-        'mock_screen_recording',
-        'file descriptor',
-        'file descriptor',
-        'file descriptor',
-        'Test Window, mock_view_capture',
-      ],
-      fixture,
-    );
-    expect(matOptions.item(0).textContent).toContain('Search test query');
-    const sfOption = matOptions.item(2);
-    expect(sfOption.textContent).toContain('Surface Flinger');
-    expect(sfOption.ariaDisabled).toEqual('true');
+    const matOptions = dom.getMatSelectPanel().findAll('mat-option');
+    await checkTooltips(Array.from(matOptions), [
+      'test query, 0',
+      'mock_screen_recording',
+      'file descriptor',
+      'file descriptor',
+      'file descriptor',
+      'Test Window, mock_view_capture',
+    ]);
+    matOptions[0].checkText('Search test query');
+    const sfOption = matOptions[2];
+    sfOption.checkText('Surface Flinger');
+    expect(sfOption.getHTMLElement().ariaDisabled).toEqual('true');
     for (const i of [1, 3, 4]) {
-      expect(matOptions.item(i).ariaDisabled).toEqual('false');
+      expect(matOptions[1].getHTMLElement().ariaDisabled).toEqual('false');
     }
 
-    matOptions.item(3).click();
-    fixture.detectChanges();
+    matOptions[3].click();
     const expectedTypes = [
       TraceType.SEARCH,
       TraceType.SCREEN_RECORDING,
@@ -389,58 +359,45 @@
       TraceType.VIEW_CAPTURE,
     ];
     expectSelectedTraceTypes(expectedTypes);
-    const traceIcons = Array.from(
-      htmlElement.querySelectorAll<HTMLElement>(
-        '#trace-selector .shown-selection .mat-icon',
-      ),
-    ).slice(1);
+    const traceIcons = dom
+      .findAll('#trace-selector .shown-selection .mat-icon')
+      .slice(1);
     traceIcons.forEach((el, index) => {
-      const text = el.textContent?.trim();
       const expectedType = expectedTypes[index];
-      expect(text).toEqual(TRACE_INFO[expectedType].icon);
+      el.checkTextExact(TRACE_INFO[expectedType].icon);
     });
-    await UnitTestUtils.checkTooltips(
-      traceIcons,
-      [
-        'Search test query',
-        'Screen Recording mock_screen_recording',
-        TRACE_INFO[TraceType.SURFACE_FLINGER].name,
-        TRACE_INFO[TraceType.PROTO_LOG].name,
-        'View Capture Test Window',
-      ],
-      fixture,
-    );
+    await checkTooltips(traceIcons, [
+      'Search test query',
+      'Screen Recording mock_screen_recording',
+      TRACE_INFO[TraceType.SURFACE_FLINGER].name,
+      TRACE_INFO[TraceType.PROTO_LOG].name,
+      'View Capture Test Window',
+    ]);
 
-    matOptions.item(3).click();
-    fixture.detectChanges();
+    matOptions[3].click();
     expectSelectedTraceTypes(allTraceTypes);
-    const newIcons = htmlElement.querySelectorAll(
-      '#trace-selector .shown-selection .mat-icon',
-    );
+    const newIcons = dom.findAll('#trace-selector .shown-selection .mat-icon');
     expect(
       Array.from(newIcons)
-        .map((icon) => icon.textContent?.trim())
+        .map((icon) => icon.getText())
         .slice(1),
     ).toEqual(allTraceTypes.map((type) => TRACE_INFO[type].icon));
   });
 
   it('update name and disables option for dumps', async () => {
-    loadAllTraces(component, fixture, false);
-    await openSelectPanel();
+    loadAllTraces(component, dom, false);
+    await dom.openMatSelect();
 
-    const matOptions =
-      document.documentElement.querySelectorAll<HTMLInputElement>('mat-option'); // [WM, SF, SR, ProtoLog, VC]
+    const matOptions = dom.getMatSelectPanel().findAll('mat-option'); // [WM, SF, SR, ProtoLog, VC]
 
     for (const i of [0, 2, 4]) {
-      expect(matOptions.item(i).ariaDisabled).toEqual('false');
+      expect(matOptions[i].getHTMLElement().ariaDisabled).toEqual('false');
     }
     for (const i of [1, 3]) {
-      expect(matOptions.item(i).ariaDisabled).toEqual('true');
+      expect(matOptions[i].getHTMLElement().ariaDisabled).toEqual('true');
     }
-    expect(matOptions.item(3).textContent).toContain('ProtoLog Dump');
-    expect(matOptions.item(4).textContent).toContain(
-      'View Capture Test Window',
-    );
+    matOptions[3].checkText('ProtoLog Dump');
+    matOptions[4].checkText('View Capture Test Window');
   });
 
   it('next button disabled if no next entry', () => {
@@ -451,22 +408,20 @@
       100n,
     );
 
-    const nextEntryButton = assertDefined(
-      htmlElement.querySelector('#next_entry_button'),
-    );
-    expect(nextEntryButton.getAttribute('disabled')).toBeFalsy();
+    const nextEntryButton = dom.get(nextEntrySelector);
+    nextEntryButton.checkDisabled(false);
 
     timelineData.setPosition(position90);
-    fixture.detectChanges();
-    expect(nextEntryButton.getAttribute('disabled')).toBeFalsy();
+    dom.detectChanges();
+    nextEntryButton.checkDisabled(false);
 
     timelineData.setPosition(position110);
-    fixture.detectChanges();
-    expect(nextEntryButton.getAttribute('disabled')).toBeTruthy();
+    dom.detectChanges();
+    nextEntryButton.checkDisabled(true);
 
     timelineData.setPosition(position112);
-    fixture.detectChanges();
-    expect(nextEntryButton.getAttribute('disabled')).toBeTruthy();
+    dom.detectChanges();
+    nextEntryButton.checkDisabled(true);
   });
 
   it('prev button disabled if no prev entry', () => {
@@ -476,36 +431,30 @@
     expect(timelineData.getCurrentPosition()?.timestamp.getValueNs()).toEqual(
       100n,
     );
-    const prevEntryButton = assertDefined(
-      htmlElement.querySelector('#prev_entry_button'),
-    );
-    expect(prevEntryButton.getAttribute('disabled')).toBeTruthy();
+    const prevEntryButton = dom.get(prevEntrySelector);
+    prevEntryButton.checkDisabled(true);
 
     timelineData.setPosition(position90);
-    fixture.detectChanges();
-    expect(prevEntryButton.getAttribute('disabled')).toBeTruthy();
+    dom.detectChanges();
+    prevEntryButton.checkDisabled(true);
 
     timelineData.setPosition(position110);
-    fixture.detectChanges();
-    expect(prevEntryButton.getAttribute('disabled')).toBeFalsy();
+    dom.detectChanges();
+    prevEntryButton.checkDisabled(false);
 
     timelineData.setPosition(position112);
-    fixture.detectChanges();
-    expect(prevEntryButton.getAttribute('disabled')).toBeFalsy();
+    dom.detectChanges();
+    prevEntryButton.checkDisabled(false);
   });
 
   it('next button enabled for different active viewers', async () => {
     loadSfWmTraces();
-    const nextEntryButton = assertDefined(
-      htmlElement.querySelector('#next_entry_button'),
-    );
-
-    expect(nextEntryButton.getAttribute('disabled')).toBeNull();
+    const nextEntryButton = dom.get(nextEntrySelector);
+    nextEntryButton.checkDisabled(false);
 
     await updateActiveTrace(TraceType.WINDOW_MANAGER);
-    fixture.detectChanges();
-
-    expect(nextEntryButton.getAttribute('disabled')).toBeNull();
+    dom.detectChanges();
+    nextEntryButton.checkDisabled(false);
   });
 
   it('changes timestamp on next entry button press', () => {
@@ -516,9 +465,7 @@
         .getCurrentPosition()
         ?.timestamp.getValueNs(),
     ).toEqual(100n);
-    const nextEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#next_entry_button'),
-    );
+    const nextEntryButton = dom.get(nextEntrySelector);
 
     testCurrentTimestampOnButtonClick(nextEntryButton, position105, 110n);
 
@@ -541,9 +488,7 @@
         .getCurrentPosition()
         ?.timestamp.getValueNs(),
     ).toEqual(100n);
-    const prevEntryButton = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#prev_entry_button'),
-    );
+    const prevEntryButton = dom.get(prevEntrySelector);
 
     // In this state we are already on the first entry at timestamp 100, so
     // there is no entry to move to before and we just don't update the timestamp
@@ -568,27 +513,22 @@
     const spyNextEntry = spyOn(timelineComponent, 'moveToNextEntry');
     const spyPrevEntry = spyOn(timelineComponent, 'moveToPreviousEntry');
 
-    document.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
-    fixture.detectChanges();
+    dom.keydownArrowRight(true);
     expect(spyNextEntry).toHaveBeenCalled();
 
-    const formElement = htmlElement.querySelector('.time-input input');
+    const formElement = dom.get('.time-input input').getHTMLElement();
     const focusInEvent = new FocusEvent('focusin');
     Object.defineProperty(focusInEvent, 'target', {value: formElement});
-    document.dispatchEvent(focusInEvent);
-    fixture.detectChanges();
+    dom.dispatchEventInDocument(focusInEvent);
 
-    document.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
-    fixture.detectChanges();
+    dom.keydownArrowLeft(true);
     expect(spyPrevEntry).not.toHaveBeenCalled();
 
     const focusOutEvent = new FocusEvent('focusout');
     Object.defineProperty(focusOutEvent, 'target', {value: formElement});
-    document.dispatchEvent(focusOutEvent);
-    fixture.detectChanges();
+    dom.dispatchEventInDocument(focusOutEvent);
 
-    document.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
-    fixture.detectChanges();
+    dom.keydownArrowLeft(true);
     expect(spyPrevEntry).toHaveBeenCalled();
   });
 
@@ -601,9 +541,7 @@
         ?.timestamp.getValueNs(),
     ).toEqual(100n);
 
-    const timeInputField = assertDefined(
-      document.querySelector<HTMLInputElement>('.time-input.nano'),
-    );
+    const timeInputField = dom.get('.time-input.nano');
 
     testCurrentTimestampOnTimeInput(
       timeInputField,
@@ -647,9 +585,7 @@
         ?.timestamp.getValueNs(),
     ).toEqual(100n);
 
-    const timeInputField = assertDefined(
-      document.querySelector<HTMLInputElement>('.time-input.human'),
-    );
+    const timeInputField = dom.get('.time-input.human');
 
     testCurrentTimestampOnTimeInput(
       timeInputField,
@@ -698,9 +634,7 @@
         ?.timestamp.valueOf(),
     ).toEqual(100n);
 
-    const timeInputField = assertDefined(
-      document.querySelector<HTMLInputElement>('.time-input.human'),
-    );
+    const timeInputField = dom.get('.time-input.human');
 
     testCurrentTimestampOnTimeInput(
       timeInputField,
@@ -719,9 +653,7 @@
         ?.timestamp.valueOf(),
     ).toEqual(100n);
 
-    const timeInputField = assertDefined(
-      document.querySelector<HTMLInputElement>('.time-input.human'),
-    );
+    const timeInputField = dom.get('.time-input.human');
 
     testCurrentTimestampOnTimeInput(
       timeInputField,
@@ -752,7 +684,7 @@
       ],
       firstTimeline,
     );
-    await openSelectPanel();
+    await dom.openMatSelect();
     clickTraceFromSelectPanel(2);
     clickTraceFromSelectPanel(3);
     clickTraceFromSelectPanel(4);
@@ -763,7 +695,10 @@
 
     const secondFixture = TestBed.createComponent(TestHostComponent);
     const secondHost = secondFixture.componentInstance;
-    loadAllTraces(secondHost, secondFixture);
+    loadAllTraces(
+      secondHost,
+      new DOMTestHelper(secondFixture, secondFixture.nativeElement),
+    );
     const secondTimeline = assertDefined(secondHost.timeline);
     expectSelectedTraceTypes(
       [TraceType.SCREEN_RECORDING, TraceType.SURFACE_FLINGER],
@@ -778,7 +713,10 @@
 
     const thirdFixture = TestBed.createComponent(TestHostComponent);
     const thirdHost = thirdFixture.componentInstance;
-    loadAllTraces(thirdHost, thirdFixture);
+    loadAllTraces(
+      thirdHost,
+      new DOMTestHelper(thirdFixture, thirdFixture.nativeElement),
+    );
     const thirdTimeline = assertDefined(thirdHost.timeline);
     expectSelectedTraceTypes(
       [
@@ -804,7 +742,7 @@
       firstTimeline,
     );
     await updateActiveTrace(TraceType.PROTO_LOG);
-    await openSelectPanel();
+    await dom.openMatSelect();
     clickTraceFromSelectPanel(1);
     clickTraceFromSelectPanel(4);
     expectSelectedTraceTypes(
@@ -818,7 +756,10 @@
 
     const secondFixture = TestBed.createComponent(TestHostComponent);
     const secondHost = secondFixture.componentInstance;
-    loadAllTraces(secondHost, secondFixture);
+    loadAllTraces(
+      secondHost,
+      new DOMTestHelper(secondFixture, secondFixture.nativeElement),
+    );
     const secondTimeline = assertDefined(secondHost.timeline);
     expectSelectedTraceTypes(
       [
@@ -834,13 +775,16 @@
   it('does not apply stored trace deselection if only one timestamp available', async () => {
     loadAllTraces();
     await updateActiveTrace(TraceType.PROTO_LOG);
-    await openSelectPanel();
+    await dom.openMatSelect();
     clickTraceFromSelectPanel(2);
 
     const secondFixture = TestBed.createComponent(TestHostComponent);
     const secondHost = secondFixture.componentInstance;
     const secondElement = secondFixture.nativeElement;
-    await loadTracesWithOneTimestamp(secondHost, secondFixture);
+    await loadTracesWithOneTimestamp(
+      secondHost,
+      new DOMTestHelper(secondFixture, secondFixture.nativeElement),
+    );
 
     const shownSelection = assertDefined(
       secondElement.querySelector('#trace-selector .shown-selection'),
@@ -861,7 +805,7 @@
       ],
       component.timeline,
     );
-    await openSelectPanel();
+    await dom.openMatSelect();
     clickTraceFromSelectPanel(3);
     clickTraceFromSelectPanel(4);
     expectSelectedTraceTypes(
@@ -873,7 +817,7 @@
       component.timeline,
     );
     await updateActiveTrace(TraceType.PROTO_LOG);
-    fixture.detectChanges();
+    dom.detectChanges();
     expectSelectedTraceTypes(
       [
         TraceType.SCREEN_RECORDING,
@@ -886,7 +830,10 @@
 
     const secondFixture = TestBed.createComponent(TestHostComponent);
     const secondHost = secondFixture.componentInstance;
-    loadAllTraces(secondHost, secondFixture);
+    loadAllTraces(
+      secondHost,
+      new DOMTestHelper(secondFixture, secondFixture.nativeElement),
+    );
     const secondTimeline = assertDefined(secondHost.timeline);
     expectSelectedTraceTypes(
       [
@@ -910,7 +857,7 @@
       ],
       component.timeline,
     );
-    await openSelectPanel();
+    await dom.openMatSelect();
     clickTraceFromSelectPanel(3);
     clickTraceFromSelectPanel(4);
     expectSelectedTraceTypes(
@@ -924,7 +871,10 @@
 
     const secondFixture = TestBed.createComponent(TestHostComponent);
     const secondHost = secondFixture.componentInstance;
-    loadSfWmTraces(secondHost, secondFixture);
+    loadSfWmTraces(
+      secondHost,
+      new DOMTestHelper(secondFixture, secondFixture.nativeElement),
+    );
     const secondTimeline = assertDefined(secondHost.timeline);
     expectSelectedTraceTypes(
       [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER],
@@ -933,7 +883,10 @@
 
     const thirdFixture = TestBed.createComponent(TestHostComponent);
     const thirdHost = thirdFixture.componentInstance;
-    loadAllTraces(thirdHost, thirdFixture);
+    loadAllTraces(
+      thirdHost,
+      new DOMTestHelper(thirdFixture, thirdFixture.nativeElement),
+    );
     const thirdTimeline = assertDefined(thirdHost.timeline);
     expectSelectedTraceTypes(
       [
@@ -951,13 +904,16 @@
       [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER],
       component.timeline,
     );
-    await openSelectPanel();
+    await dom.openMatSelect();
     clickTraceFromSelectPanel(1);
     expectSelectedTraceTypes([TraceType.SURFACE_FLINGER], component.timeline);
 
     const secondFixture = TestBed.createComponent(TestHostComponent);
     const secondHost = secondFixture.componentInstance;
-    loadAllTraces(secondHost, secondFixture);
+    loadAllTraces(
+      secondHost,
+      new DOMTestHelper(secondFixture, secondFixture.nativeElement),
+    );
     const secondTimeline = assertDefined(secondHost.timeline);
     expectSelectedTraceTypes(
       [
@@ -976,17 +932,12 @@
     expect(timelineComponent.bookmarks).toEqual([]);
     expect(timelineComponent.currentPositionBookmarked()).toBeFalse();
 
-    const bookmarkIcon = assertDefined(
-      htmlElement.querySelector<HTMLElement>('.bookmark-icon'),
-    );
-    bookmarkIcon.click();
-    fixture.detectChanges();
+    const bookmarkIcon = dom.findAndClick('.bookmark-icon');
 
     expect(timelineComponent.bookmarks).toEqual([time100]);
     expect(timelineComponent.currentPositionBookmarked()).toBeTrue();
 
     bookmarkIcon.click();
-    fixture.detectChanges();
     expect(timelineComponent.bookmarks).toEqual([]);
     expect(timelineComponent.currentPositionBookmarked()).toBeFalse();
   });
@@ -1029,7 +980,7 @@
     loadSfWmTraces();
     const timelineComponent = assertDefined(component.timeline);
     timelineComponent.bookmarks = [time100, time101, time112];
-    fixture.detectChanges();
+    dom.detectChanges();
 
     openContextMenu();
     clickRemoveAllBookmarksOption();
@@ -1063,10 +1014,8 @@
     ).and.returnValue(Promise.resolve(trace));
     const canvas = miniTimelineComponent.getCanvas();
     canvas.dispatchEvent(new MouseEvent('mousedown'));
-    fixture.detectChanges();
-    await fixture.whenStable();
-    fixture.detectChanges();
-    await fixture.whenStable();
+    await dom.detectChangesAndWaitStable();
+    await dom.detectChangesAndWaitStable();
 
     expect(activeTrace).toEqual(trace);
     expect(position).toBeDefined();
@@ -1112,11 +1061,10 @@
     loadSfWmTraces();
     const timelineComponent = assertDefined(component.timeline);
     timelineComponent.isDisabled = true;
-    fixture.detectChanges();
+    dom.detectChanges();
 
     const spyNextEntry = spyOn(timelineComponent, 'moveToNextEntry');
-    document.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
-    fixture.detectChanges();
+    dom.keydownArrowRight(true);
     expect(spyNextEntry).not.toHaveBeenCalled();
   });
 
@@ -1131,10 +1079,7 @@
     spyOnProperty(wheelEvent, 'deltaY').and.returnValue(-200);
     spyOnProperty(wheelEvent, 'deltaX').and.returnValue(0);
     spyOnProperty(wheelEvent, 'y').and.returnValue(10);
-    assertDefined(htmlElement.querySelector('single-timeline')).dispatchEvent(
-      wheelEvent,
-    );
-    fixture.detectChanges();
+    dom.get('single-timeline').dispatchEvent(wheelEvent);
     expect(expandedDrawSpy).toHaveBeenCalledTimes(5); // 3 entries total + 2 selected
     expect(miniDrawSpy).toHaveBeenCalledTimes(1); // all on one canvas so spy called once
 
@@ -1142,12 +1087,9 @@
     expandedDrawSpy.calls.reset();
     miniDrawSpy.calls.reset();
     spyOnProperty(wheelEvent, 'target').and.returnValue(
-      assertDefined(htmlElement.querySelector('#mini-timeline-canvas')),
+      dom.get('#mini-timeline-canvas').getHTMLElement(),
     );
-    assertDefined(htmlElement.querySelector('mini-timeline')).dispatchEvent(
-      wheelEvent,
-    );
-    fixture.detectChanges();
+    dom.get('mini-timeline').dispatchEvent(wheelEvent);
     expect(expandedDrawSpy).toHaveBeenCalledTimes(4); // 2 entries total + 2 selected
     expect(miniDrawSpy).toHaveBeenCalledTimes(1);
   });
@@ -1161,15 +1103,12 @@
     const clickEvent = new MouseEvent('mousedown');
     spyOnProperty(clickEvent, 'offsetX').and.returnValue(0);
     spyOnProperty(clickEvent, 'offsetY').and.returnValue(0);
-    assertDefined(
-      htmlElement.querySelector<HTMLElement>('single-timeline #canvas'),
-    ).dispatchEvent(clickEvent);
-    fixture.detectChanges();
+    dom.get('single-timeline #canvas').dispatchEvent(clickEvent);
     expect(expandedDrawSpy).toHaveBeenCalledTimes(3); // redraws SF timeline row
     expect(miniDrawSpy).toHaveBeenCalledTimes(1); // all on one canvas so spy called once
   });
 
-  function loadSfWmTraces(hostComponent = component, hostFixture = fixture) {
+  function loadSfWmTraces(hostComponent = component, domHelper = dom) {
     const traces = new TracesBuilder()
       .setTimestamps(TraceType.SURFACE_FLINGER, [time100, time110])
       .setTimestamps(TraceType.WINDOW_MANAGER, [
@@ -1188,12 +1127,12 @@
     );
     timelineData.setPosition(position100);
     hostComponent.allTraces = hostComponent.timelineData.getTraces();
-    hostFixture.detectChanges();
+    domHelper.detectChanges();
   }
 
   function loadAllTraces(
     hostComponent = component,
-    hostFixture = fixture,
+    domHelper = dom,
     loadAllTraces = true,
   ) {
     const traces = new TracesBuilder()
@@ -1235,7 +1174,7 @@
       TimestampConverterUtils.TIMESTAMP_CONVERTER,
     );
     hostComponent.allTraces = traces;
-    hostFixture.detectChanges();
+    domHelper.detectChanges();
   }
 
   function loadTracesWithLargeTimeRange() {
@@ -1262,7 +1201,7 @@
     );
     timelineData.setPosition(position100);
     component.allTraces = timelineData.getTraces();
-    fixture.detectChanges();
+    dom.detectChanges();
   }
 
   function getLoadedTrace(type: TraceType): Trace<object> {
@@ -1275,7 +1214,7 @@
 
   async function loadTracesWithOneTimestamp(
     hostComponent = component,
-    hostFixture = fixture,
+    domHelper = dom,
   ) {
     const traces = new TracesBuilder()
       .setTimestamps(TraceType.SURFACE_FLINGER, [])
@@ -1287,9 +1226,8 @@
       TimestampConverterUtils.TIMESTAMP_CONVERTER,
     );
     hostComponent.allTraces = traces;
-    hostFixture.detectChanges();
-    await hostFixture.whenStable();
-    hostFixture.detectChanges();
+    await domHelper.detectChangesAndWaitStable();
+    domHelper.detectChanges();
   }
 
   async function updateActiveTrace(type: TraceType) {
@@ -1311,121 +1249,101 @@
   }
 
   function testCurrentTimestampOnButtonClick(
-    button: HTMLElement,
+    button: DOMTestHelper<TestHostComponent>,
     pos: TracePosition,
     expectedNs: bigint,
   ) {
     const timelineData = assertDefined(component.timelineData);
     timelineData.setPosition(pos);
-    fixture.detectChanges();
+    dom.detectChanges();
     button.click();
-    fixture.detectChanges();
     expect(timelineData.getCurrentPosition()?.timestamp.getValueNs()).toEqual(
       expectedNs,
     );
   }
 
   function testCurrentTimestampOnTimeInput(
-    inputField: HTMLInputElement,
+    inputField: DOMTestHelper<TestHostComponent>,
     pos: TracePosition,
     textInput: string,
     expectedNs: bigint,
   ) {
     const timelineData = assertDefined(component.timelineData);
     timelineData.setPosition(pos);
-    fixture.detectChanges();
+    dom.detectChanges();
 
-    inputField.value = textInput;
+    inputField.updateValue(textInput);
     inputField.dispatchEvent(new Event('change'));
-    fixture.detectChanges();
 
     expect(timelineData.getCurrentPosition()?.timestamp.getValueNs()).toEqual(
       expectedNs,
     );
   }
 
-  async function openSelectPanel() {
-    const selectTrigger = assertDefined(
-      htmlElement.querySelector<HTMLElement>('.mat-select-trigger'),
-    );
-    selectTrigger.click();
-    fixture.detectChanges();
-    await fixture.whenStable();
-  }
-
   function clickTraceFromSelectPanel(index: number) {
-    const matOptions = assertDefined(
-      document.documentElement.querySelectorAll<HTMLElement>('mat-option'),
-    );
-    matOptions.item(index).click();
-    fixture.detectChanges();
+    dom.getMatSelectPanel().findAndClickByIndex('mat-option', index);
   }
 
   function checkActiveTraceSurfaceFlinger(
-    nextEntryButton: HTMLElement,
-    prevEntryButton: HTMLElement,
+    nextEntryButton: DOMTestHelper<TestHostComponent>,
+    prevEntryButton: DOMTestHelper<TestHostComponent>,
   ) {
     testCurrentTimestampOnButtonClick(prevEntryButton, position110, 100n);
-    expect(prevEntryButton.getAttribute('disabled')).toEqual('true');
-    expect(nextEntryButton.getAttribute('disabled')).toBeNull();
+    prevEntryButton.checkDisabled(true);
+    nextEntryButton.checkDisabled(false);
     testCurrentTimestampOnButtonClick(nextEntryButton, position100, 110n);
-    expect(prevEntryButton.getAttribute('disabled')).toBeNull();
-    expect(nextEntryButton.getAttribute('disabled')).toEqual('true');
+    prevEntryButton.checkDisabled(false);
+    nextEntryButton.checkDisabled(true);
   }
 
   function checkActiveTraceWindowManager(
-    nextEntryButton: HTMLElement,
-    prevEntryButton: HTMLElement,
+    nextEntryButton: DOMTestHelper<TestHostComponent>,
+    prevEntryButton: DOMTestHelper<TestHostComponent>,
   ) {
     testCurrentTimestampOnButtonClick(prevEntryButton, position90, 90n);
-    expect(prevEntryButton.getAttribute('disabled')).toEqual('true');
-    expect(nextEntryButton.getAttribute('disabled')).toBeNull();
+    prevEntryButton.checkDisabled(true);
+    nextEntryButton.checkDisabled(false);
     testCurrentTimestampOnButtonClick(nextEntryButton, position90, 101n);
-    expect(prevEntryButton.getAttribute('disabled')).toBeNull();
-    expect(nextEntryButton.getAttribute('disabled')).toBeNull();
+    prevEntryButton.checkDisabled(false);
+    nextEntryButton.checkDisabled(false);
     testCurrentTimestampOnButtonClick(nextEntryButton, position110, 112n);
-    expect(prevEntryButton.getAttribute('disabled')).toBeNull();
-    expect(nextEntryButton.getAttribute('disabled')).toEqual('true');
+    prevEntryButton.checkDisabled(false);
+    nextEntryButton.checkDisabled(true);
   }
 
   function checkActiveTraceHasOneEntry(
-    nextEntryButton: HTMLElement,
-    prevEntryButton: HTMLElement,
+    nextEntryButton: DOMTestHelper<TestHostComponent>,
+    prevEntryButton: DOMTestHelper<TestHostComponent>,
   ) {
-    expect(prevEntryButton.getAttribute('disabled')).toEqual('true');
-    expect(nextEntryButton.getAttribute('disabled')).toEqual('true');
+    prevEntryButton.checkDisabled(true);
+    nextEntryButton.checkDisabled(true);
   }
 
   function checkNoTimelineNavigation() {
     const timelineComponent = assertDefined(component.timeline);
     // no expand button
     expect(
-      htmlElement.querySelector(`.${timelineComponent.TOGGLE_BUTTON_CLASS}`),
-    ).toBeNull();
+      dom.find(`.${timelineComponent.TOGGLE_BUTTON_CLASS}`),
+    ).toBeUndefined();
 
     // no timelines shown
-    const miniTimelineElement = fixture.debugElement.query(
-      By.directive(MiniTimelineComponent),
-    );
-    expect(miniTimelineElement).toBeFalsy();
+    const miniTimelineElement = dom.findByDirective(MiniTimelineComponent);
+    expect(miniTimelineElement).toBeUndefined();
 
     // arrow key presses don't do anything
     const spyNextEntry = spyOn(timelineComponent, 'moveToNextEntry');
     const spyPrevEntry = spyOn(timelineComponent, 'moveToPreviousEntry');
 
-    document.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
-    fixture.detectChanges();
+    dom.keydownArrowRight(true);
     expect(spyNextEntry).not.toHaveBeenCalled();
 
-    document.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
-    fixture.detectChanges();
+    dom.keydownArrowLeft(true);
     expect(spyPrevEntry).not.toHaveBeenCalled();
   }
 
   function openContextMenu(xOffset = 0, clickBelowMarker = false) {
-    const miniTimelineCanvas = assertDefined(
-      htmlElement.querySelector<HTMLElement>('#mini-timeline-canvas'),
-    );
+    const miniTimelineCanvas = dom.get('#mini-timeline-canvas');
+    const canvasEl = miniTimelineCanvas.getHTMLElement();
     const yOffset = clickBelowMarker
       ? assertDefined(component.timeline?.miniTimeline?.drawer?.getHeight()) /
           6 +
@@ -1434,53 +1352,37 @@
 
     const event = new MouseEvent('contextmenu');
     spyOnProperty(event, 'offsetX').and.returnValue(
-      miniTimelineCanvas.offsetLeft +
-        miniTimelineCanvas.offsetWidth / 2 +
-        xOffset,
+      canvasEl.offsetLeft + canvasEl.offsetWidth / 2 + xOffset,
     );
     spyOnProperty(event, 'offsetY').and.returnValue(
-      miniTimelineCanvas.offsetTop + yOffset,
+      canvasEl.offsetTop + yOffset,
     );
     miniTimelineCanvas.dispatchEvent(event);
-    fixture.detectChanges();
   }
 
   function clickToggleBookmarkOption() {
-    const menu = assertDefined(document.querySelector('.context-menu'));
-    const toggleOption = assertDefined(
-      menu.querySelector<HTMLElement>('.context-menu-item'),
-    );
-    toggleOption.click();
-    fixture.detectChanges();
+    const menu = dom.getInDocument('.context-menu');
+    menu.findAndClick('.context-menu-item');
   }
 
   function clickRemoveAllBookmarksOption() {
-    const menu = assertDefined(document.querySelector('.context-menu'));
-    const options = assertDefined(
-      menu.querySelectorAll<HTMLElement>('.context-menu-item'),
-    );
-    options.item(1).click();
-    fixture.detectChanges();
+    const menu = dom.getInDocument('.context-menu');
+    menu.findAndClickByIndex('.context-menu-item', 1);
   }
 
   function checkTimelineEnabled() {
-    expect(htmlElement.querySelector('.disabled-component')).toBeNull();
-    expect(htmlElement.querySelector('.disabled-message')).toBeNull();
+    expect(dom.find('.disabled-component')).toBeUndefined();
+    expect(dom.find('.disabled-message')).toBeUndefined();
   }
 
   function checkTimelineDisabled() {
-    expect(htmlElement.querySelector('.disabled-component')).toBeTruthy();
-    expect(htmlElement.querySelector('.disabled-message')).toBeTruthy();
+    expect(dom.find('.disabled-component')).toBeDefined();
+    expect(dom.find('.disabled-message')).toBeDefined();
   }
 
   function openExpandedTimeline() {
     const timelineComponent = assertDefined(component.timeline);
-    assertDefined(
-      htmlElement.querySelector<HTMLElement>(
-        `.${timelineComponent.TOGGLE_BUTTON_CLASS}`,
-      ),
-    ).click();
-    fixture.detectChanges();
+    dom.findAndClick(`.${timelineComponent.TOGGLE_BUTTON_CLASS}`);
   }
 
   @Component({