angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.73k forks source link

Cannot check mat-checkbox by calling click() in Protractor E2E tests #16640

Closed micobarac closed 5 years ago

micobarac commented 5 years ago

Reproduction

protractor.conf.js

// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

const { SpecReporter } = require('jasmine-spec-reporter');

exports.config = {
  allScriptsTimeout: 11000,
  specs: ['./e2e/**/*.e2e-spec.ts'],
  capabilities: {
    browserName: 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {}
  },
  onPrepare() {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });

    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));

    by.addLocator('formControlName', function(value, opt_parentElement, opt_rootSelector) {
      var using = opt_parentElement || document;
      return using.querySelectorAll('[formControlName="' + value + '"]');
    });

    browser.driver
      .manage()
      .window()
      .maximize();
  }
};

service.component.html

 <div mat-content class="p-all">
  <form [formGroup]="form" novalidate (ngSubmit)="save(form.value);">
    <div fxLayout.gt-sm="row" fxLayout.gt-xs="column" fxLayout.xs="column">
      <div fxFlex.gt-md="50" fxFlex.gt-sm="75" fxFlex.gt-xs="100" fxFlex.xs="100">
        <mat-card fxLayout="column">
          <mat-card-title>{{ 'Service.Service' | translate }}</mat-card-title>
          <mat-card-content fxLayout="column">

            <!-- Service Name -->
            <mat-form-field>
              <input matInput type="text" formControlName="name" required placeholder="{{ 'Service.Name' | translate }}"
                autocomplete="off" />
            </mat-form-field>
            <small *ngIf="name.touched && !name.value.length" class="mat-warn">
              {{ 'Service.NameRequired' | translate }}
            </small>
            <small *ngIf="name.touched && !name.valid && name.value.length" class="mat-warn">
              {{ 'App.invalidNameFormat' | translate }}
            </small>

            <mat-divider></mat-divider>

            <!-- Service Nice -->
            <mat-checkbox formControlName="nice" class="m-t-5">
              <span>{{ 'Service.Nice' | translate }}</span>
            </mat-checkbox>
          </mat-card-content>
        </mat-card>
      </div>
    </div>
    <div fxLayout.gt-sm="row" fxLayout.gt-xs="column" fxLayout.xs="column">
      <div fxFlex.gt-md="50" fxFlex.gt-sm="75" fxFlex.gt-xs="100" fxFlex.xs="100" fxLayoutAlign="end center"
        class="p-all-sm">
        <a [routerLink]="['/service']" mat-raised-button class="m-r-10">
          <mat-icon class="mat-18">undo</mat-icon> {{ 'Service.Back' | translate }}
        </a>
        <button type="submit" mat-raised-button class="mat-accent mv-from-left"
          [disabled]="!form.valid || (isLoading | async)?.show">
          <mat-icon class="mat-18">save</mat-icon>
          {{ 'Service.Save' | translate }}
        </button>
      </div>
    </div>
  </form>
</div>

service.po.ts

import { by, element, ElementFinder } from 'protractor';
import { E2E } from '../util/e2e';

export class ServicePage {
  readonly route: string = 'service';
  readonly name: string = 'Service12345';
  readonly nice: boolean = true;
  readonly e2e: E2E = new E2E();

  getName(): ElementFinder {
    return element(by.formControlName('name'));
  }

  setName(name: string) {
    const elem: ElementFinder = this.getName();

    elem.clear();
    elem.sendKeys(name);
  }

  getNice(): ElementFinder {
    return element(by.formControlName('nice'));
  }

  setNice(value: boolean) {
    const elem: ElementFinder = this.getNice();

    elem.isSelected().then((isSelected: boolean) => {
      if (isSelected !== value) {
        elem.click();
      }
    });
  }

  inputEmpty() {
    this.setName('');
  }

  inputNonEmpty() {
    this.setName(this.name);
    this.setNice(this.nice);
  }

  submit() {
    this.e2e.waitForLoader();
    this.e2e.getSubmit().click();
  }

  back() {
    this.e2e.navigateTo(`/${this.route}`);
  }
}

service.e2e-spec.ts

import { LoginPage } from '../login/login.po';
import { E2E } from '../util/e2e';
import { ServicePage } from './service.po';

describe('Service', () => {
  const e2e: E2E = new E2E();
  const page: ServicePage = new ServicePage();
  const loginPage: LoginPage = new LoginPage();

  beforeAll(() => {
    loginPage.login();
  });

  it('Sidebar should display a service link', () => {
    expect(e2e.getSidebarItem(page.route).isPresent()).toBe(true);
  });

  it('Service page should display a data table', () => {
    e2e.getSidebarItem(page.route).click();
    e2e.waitForUrl(`/${page.route}`);
    expect(e2e.getDataTable().isPresent()).toBe(true);
  });

  it('Add button should display an input form', () => {
    e2e.getDataTableButton('add').click();
    e2e.waitForUrl(`/${page.route}/add`);
    expect(e2e.getForm().isPresent()).toBe(true);
  });

  it('Form should be invalid with empty fields', () => {
    page.inputEmpty();

    const status = e2e.getForm().getAttribute('class');
    expect(status).toContain('ng-invalid');
  });

  it('Form should be valid with non-empty fields', () => {
    page.inputNonEmpty();

    const status = e2e.getForm().getAttribute('class');
    expect(status).toContain('ng-valid');
  });

  it('Submit action should create a new record', () => {
    page.inputNonEmpty();
    page.submit();

    e2e.waitForMessageBar();
    e2e.expectMessageBarType('info');
    e2e.expectUrl(`/${page.route}`);
  });

  afterEach(e2e.checkForErrors());

  afterAll(() => {
    loginPage.logout();
  });
});

Expected Behavior

I expect elem.click() to work in Protractor on mat-checkbox.

If I switch to native HTML checkbox, elem.click() works just fine.

<input type="checkbox" formControlName="nice" class="m-t-5"> <span>{{ 'Service.Nice' | translate }}</span>

Actual Behavior

No errors, but mat-checkbox stays unchecked, no matter what.

<mat-checkbox _ngcontent-hrw-c26="" class="m-t-5 mat-checkbox mat-accent ng-untouched ng-pristine ng-valid" formcontrolname="nice" ng-reflect-name="nice" id="mat-checkbox-1">
    <label class="mat-checkbox-layout" for="mat-checkbox-1-input">
        <div class="mat-checkbox-inner-container">
            <input class="mat-checkbox-input cdk-visually-hidden" type="checkbox" id="mat-checkbox-1-input" tabindex="0" aria-checked="false">
            <div class="mat-checkbox-ripple mat-ripple" matripple="" ng-reflect-centered="true" ng-reflect-radius="20" ng-reflect-animation="[object Object]" ng-reflect-disabled="false" ng-reflect-trigger="[object HTMLLabelElement]">
                <div class="mat-ripple-element mat-checkbox-persistent-ripple"></div>
            </div>
            <div class="mat-checkbox-frame"></div>
            <div class="mat-checkbox-background">
                <svg xml:space="preserve" class="mat-checkbox-checkmark" focusable="false" version="1.1" viewBox="0 0 24 24">
                    <path class="mat-checkbox-checkmark-path" d="M4.1,12.7 9,17.6 20.3,6.3" fill="none" stroke="white"></path>
                </svg>
                <div class="mat-checkbox-mixedmark"></div>
            </div>
        </div><span class="mat-checkbox-label"><span style="display:none">&nbsp;</span><span _ngcontent-hrw-c26="">Nice</span></span>
    </label>
</mat-checkbox>

Environment

Angular CLI: 8.1.2
Node: 10.16.0
OS: darwin x64
Angular: 8.1.2
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.801.2
@angular-devkit/build-angular     0.801.2
@angular-devkit/build-optimizer   0.801.2
@angular-devkit/build-webpack     0.801.2
@angular-devkit/core              8.1.2
@angular-devkit/schematics        8.1.2
@angular/cdk                      8.1.1
@angular/flex-layout              8.0.0-beta.26
@angular/material                 8.1.1
@ngtools/webpack                  8.1.2
@schematics/angular               8.1.2
@schematics/update                0.801.2
rxjs                              6.5.2
typescript                        3.4.5
webpack                           4.35.2

package.json

{
  "name": "project",
  "version": "2.2.2",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve -o",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^8.1.2",
    "@angular/cdk": "^8.1.1",
    "@angular/common": "^8.1.2",
    "@angular/compiler": "^8.1.2",
    "@angular/core": "^8.1.2",
    "@angular/flex-layout": "^8.0.0-beta.26",
    "@angular/forms": "^8.1.2",
    "@angular/material": "^8.1.1",
    "@angular/platform-browser": "^8.1.2",
    "@angular/platform-browser-dynamic": "^8.1.2",
    "@angular/router": "^8.1.2",
    "@auth0/angular-jwt": "^3.0.0",
    "@ngstack/translate": "^2.0.1",
    "bytes": "^3.1.0",
    "case": "^1.6.1",
    "chart.js": "^2.8.0",
    "classlist.js": "^1.1.20150312",
    "date-fns": "^1.30.1",
    "hammerjs": "^2.0.8",
    "lz-string": "^1.4.4",
    "material-design-icons-iconfont": "^5.0.1",
    "ng2-charts": "^2.3.0",
    "ngx-take-until-destroy": "^5.4.0",
    "rxjs": "^6.5.2",
    "web-animations-js": "^2.3.2",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.801.2",
    "@angular/cli": "^8.1.2",
    "@angular/compiler-cli": "^8.1.2",
    "@angular/language-service": "^8.1.2",
    "@types/bytes": "^3.0.0",
    "@types/jasmine": "^3.3.14",
    "@types/jasminewd2": "^2.0.6",
    "@types/lodash": "^4.14.136",
    "@types/lz-string": "^1.3.33",
    "@types/moment-duration-format": "^2.2.2",
    "@types/node": "^12.6.8",
    "codelyzer": "^5.1.0",
    "jasmine": "^3.4.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.2.0",
    "karma-chrome-launcher": "~3.0.0",
    "karma-cli": "~2.0.0",
    "karma-coverage-istanbul-reporter": "^2.0.6",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "^1.4.2",
    "prettier": "1.18.2",
    "protractor": "~5.4.2",
    "ts-node": "~8.3.0",
    "tslint": "~5.18.0",
    "typescript": "3.4.5"
  }
}
micobarac commented 5 years ago

Anyone knows how to handle this?

micobarac commented 5 years ago

Actually, I found out what the problem was. If you let mat-chekbox to flex out to available parent space, clicking on it (without clicking on label) won't do any effective checbox action. So, I wrapped the mat-checkbox inside flex container, causing it's width to be auto, and the click() started working...

angular-automatic-lock-bot[bot] commented 5 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.