Search code examples
angulartypescriptkarma-jasminegithub-actionsangular-test

Running Angular tests in GitHub Actions fails when reading inner text (passes locally)


I have a GitHub actions workflow that runs Karma for an Angular project. When the test tries to read the 'innerText' of a native element. The error 'TypeError: Cannot read properties of null (reading 'innerText')' is thrown, which I assume is because the query selector is unable to find the elements. However, the tests pass locally. Furthermore, there are other query selector tests that are passing in the virtual environment.

Local: enter image description here

Actions: enter image description here

HTML:

<mat-tab-group>
  <mat-tab>
    <ng-template mat-tab-label>
      <mat-icon
        [matTooltipDisabled]="(isWeb | async)!.matches"
        [matTooltipShowDelay]="0"
        matTooltip="Membership"
        matTooltipPosition="after"
      >groups
      </mat-icon>
      <mat-label *ngIf="(isWeb | async)?.matches" class="tab-mat-label">Membership</mat-label>
    </ng-template>
    Content 1
  </mat-tab>

  <mat-tab>
    <ng-template mat-tab-label>
      <mat-icon
        [matTooltipDisabled]="(isWeb | async)!.matches"
        [matTooltipShowDelay]="0"
        matTooltip="Rosters"
        matTooltipPosition="after">calendar_month
      </mat-icon>
      <mat-label *ngIf="(isWeb | async)?.matches" class="tab-mat-label">Rosters</mat-label>
    </ng-template>
    Content 2
  </mat-tab>

  <mat-tab>
    <ng-template mat-tab-label>
      <mat-icon
        [matTooltipDisabled]="(isWeb | async)!.matches"
        [matTooltipShowDelay]="0"
        matTooltip="Notifications"
        matTooltipPosition="after">edit_notifications
      </mat-icon>
      <mat-label *ngIf="(isWeb | async)?.matches" class="tab-mat-label">Notifications</mat-label>
    </ng-template>

    Content 3
  </mat-tab>

  <mat-tab>
    <ng-template mat-tab-label>
      <mat-icon
        [matTooltipDisabled]="(isWeb | async)!.matches"
        [matTooltipShowDelay]="0"
        matTooltip="Security"
        matTooltipPosition="after">shields
      </mat-icon>
      <mat-label *ngIf="(isWeb | async)?.matches" class="tab-mat-label">Security</mat-label>
    </ng-template>

    Content 3
  </mat-tab>
</mat-tab-group>

Component Code:

import { Component, OnInit } from '@angular/core';
import {BreakpointObserver, Breakpoints, BreakpointState} from "@angular/cdk/layout";
import {Observable} from "rxjs";

@Component({
  selector: 'racs-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.scss']
})
export class AdminComponent implements OnInit {

  constructor(private _breakpointObserver: BreakpointObserver) { }

  public get isWeb(): Observable<BreakpointState> {
    return this._breakpointObserver.observe(Breakpoints.Web);
  }

  ngOnInit(): void {
  }

}

Test code:


import {AdminComponent} from './admin.component';
import {RacsAdminModule} from "./racs-admin.module";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {BreakpointObserver} from "@angular/cdk/layout";

describe('AdminComponent', () => {
  let component: AdminComponent;
  let fixture: ComponentFixture<AdminComponent>;
  let compiled: any;
  const tabLabelIconPairs: { [key: string]: string; } = {
    Membership: 'groups',
    Rosters: 'calendar_month',
    Notifications: 'edit_notifications',
    Security: 'shields',
  }

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [AdminComponent],
      imports: [RacsAdminModule, BrowserAnimationsModule],
      providers: [BreakpointObserver]
    })
      .compileComponents();

    fixture = TestBed.createComponent(AdminComponent);
    compiled = fixture.nativeElement;
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create and show tabs with labels in Web View', () => {
    expect(component).toBeTruthy();
  });

  it('should show tab labels and icons', function () {
    let tabGroup = compiled.querySelector('mat-tab-group');
    let tabs: any[] = compiled.querySelectorAll('[role="tab"]');
    expect(component).toBeTruthy();
    expect(tabGroup).toBeTruthy();
    expect(tabs.length).toBe(Object.keys(tabLabelIconPairs).length);

    // Check labels and icons for each tab
    tabs.forEach((tab: any) => {
      const label: string = tab.querySelector('mat-label').innerText;
      const icon: string = tab.querySelector('mat-icon').innerText;

      expect(label in tabLabelIconPairs).toBeTruthy();
      expect(tabLabelIconPairs[label]).toBe(icon)
    });
  });
});

Solution

  • I think it's with isWeb | async. By the looks of it, the *ngIf's are the only ones that could affect them being rendered. So maybe you should first wait for the async stuff to finish before querying them. Deploying --prod and on server could increase query times, so on local it may query faster and tests pass.