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

Time:07-01

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" >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" >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" >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" >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)
    });
  });
});

CodePudding user response:

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.

CodePudding user response:

As pointed out by Joosep Parts the issue was the async nature of how the elements were being rendered. This was solved by creating a BreakpointObserver stud and spying on it. When the observe method was called, a fixture.detectChange() call was made and the tests passed. Note that this was wrapped in a fixture.wheStable() block

Observer stud:

export class BreakpointObserverStub {
  observe(): BreakpointState {
    return {
      matches: true,
      breakpoints: {
        "web": true
      }
    } as BreakpointState
  }
}

Test:

it('Should show the correct tab labels and icons in Web View', waitForAsync(() => {
    fixture.whenStable().then(() => {
      spyOn(observer, 'observe').and.returnValue(of({
        matches: true,
        breakpoints: {
          "web": true
        }
      } as BreakpointState));

      // Wait for changes and confirm observe was called
      fixture.detectChanges();
      expect(observer.observe).toHaveBeenCalled();

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

        // Check labels and icons for each tab
        expect(label in tabLabelIconPairs).toBeTruthy();
        expect(tabLabelIconPairs[label]).toBe(icon)
      });
    });
  }));
  • Related