千家信息网

Angular单元测试与E2E测试

发表于:2025-12-02 作者:千家信息网编辑
千家信息网最后更新 2025年12月02日,本文介绍了Angular 7单元测试和E2E测试的配置与测试方法。使用Angular CLI新建工程,已配置好基础测试环境,生成了测试样例代码。默认,Angular单元测试使用Jasmine测试框架和
千家信息网最后更新 2025年12月02日Angular单元测试与E2E测试

本文介绍了Angular 7单元测试和E2E测试的配置与测试方法。使用Angular CLI新建工程,已配置好基础测试环境,生成了测试样例代码。默认,Angular单元测试使用Jasmine测试框架和Karma测试运行器,E2E测试使用Jasmine和Protractor测试框架。

配置单元测试

Jasmine是用于测试JavaScript的行为驱动(Behavior-Driven)框架,不依赖于任何其他JavaScript框架。
Karma是测试运行器,为开发人员提供了高效、真实的测试环境,支持多种浏览器,易于调试。

配置文件

单元测试配置文件test.ts和karma.conf.js:
test.ts

import 'zone.js/dist/zone-testing';import { getTestBed } from '@angular/core/testing';import {  BrowserDynamicTestingModule,  platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';declare const require: any;// First, initialize the Angular testing environment.getTestBed().initTestEnvironment(  BrowserDynamicTestingModule,  platformBrowserDynamicTesting());// Then we find all the tests.const context = require.context('./', true, /\.spec\.ts$/);// And load the modules.context.keys().map(context);

默认,测试文件扩展名必须为.spec.ts。
karma.conf.js

module.exports = function (config) {  config.set({    basePath: '',    frameworks: ['jasmine', '@angular-devkit/build-angular'],    plug×××: [      require('karma-jasmine'),      require('karma-chrome-launcher'),      require('karma-jasmine-html-reporter'),      require('karma-coverage-istanbul-reporter'),      require('@angular-devkit/build-angular/plug×××/karma')    ],    client: {      clearContext: false // leave Jasmine Spec Runner output visible in browser    },    coverageIstanbulReporter: {      dir: require('path').join(__dirname, '../coverage'),      reports: ['html', 'lcovonly'],      fixWebpackSourcePaths: true    },    reporters: ['progress', 'kjhtml'],    port: 9876,    colors: true,    logLevel: config.LOG_INFO,    autoWatch: true,    browsers: ['Chrome'],    singleRun: false  });};

默认,使用Chrome浏览器,可生成单元测试报告和覆盖率报告,覆盖率报告保存在根目录coverage文件夹内,启用autoWatch。
singleRun默认为false,如设为true则测试结束后会自动退出并根据测试结果返回代码0或1,常用于CI环境。

浏览器配置

Karma支持的浏览器:

  • Chrome
  • ChromeCanary
  • ChromeHeadless
  • PhantomJS
  • Firefox
  • Opera
  • IE
  • Safari

可同时配置多个浏览器进行测试,要启用其他浏览器,需安装依赖,比如启用Firefox:

npm i karma-firefox-launcher --save-dev

然后在karma.conf.js内增加配置:

...require('karma-chrome-launcher'),require('karma-firefox-launcher'),...browsers: ['Chrome', 'Firefox'],...

运行测试

用CLI创建App生成了一个单元测试文件app.component.spec.ts。执行CLI命令ng test即可运行单元测试:

ng test

运行后在控制台输出测试结果并打开浏览器:

浏览器会显示测试结果,总测试数,失败数。在顶部,每个点或叉对应一个测试用例,点表示成功,叉表示失败,鼠标移到点或叉上会显示测试信息。点击测试结果中的某一行,可重新运行某个或某组(测试套件)测试。

常用参数:
--browsers 指定使用的浏览器
--code-coverage 输出覆盖率报告
--code-coverage-exclude 排除文件或路径
--karma-config 指定Karma配置文件
--prod 启用production环境
--progress 默认为true,将编译进度输出到控制台
--watch 默认为true,代码修改后会重新运行测试

自定义Launcher

karma-chrome-launcher、karma-firefox-launcher、karma-ie-launcher等均支持自定义Launcher,customLaunchers与--browsers结合使用可满足多种环境的测试需求。每种浏览器支持的自定义属性请查看Karma Browsers文档。
比如,CI环境下常用Headless模式,不需显示浏览器界面,在karma.conf.js中增加如下配置:

browsers: ['Chrome'],customLaunchers: {  ChromeHeadlessCI: {    base: 'ChromeHeadless',    flags: ['--no-sandbox']  }},

运行如下命令进行测试:

ng test --watch=false --progress=false --browsers=ChromeHeadlessCI

测试覆盖率

运行如下命令生成测试覆盖率报告,报告保存在项目根目录下的coverage文件夹内:

ng test --watch=false --code-coverage

如想每次测试都生成报告,可修改CLI配置文件angular.json:

"test": {  "options": {    "codeCoverage": true  }}

设置排除的文件或路径

ng test --watch=false --code-coverage --code-coverage-exclude=src/app/heroes/heroes.component.ts --code-coverage-exclude=src/app/hero-search/*

同样可以在angular.json中配置:

"test": {  "options": {    "codeCoverage": true,    "codeCoverageExclude": ["src/app/heroes/heroes.component.ts", "src/app/hero-search/*"]  }}

设定测试覆盖率指标
编辑配置文件karma.conf.js,增加如下内容:

coverageIstanbulReporter: {  reports: [ 'html', 'lcovonly' ],  fixWebpackSourcePaths: true,  thresholds: {    statements: 80,    lines: 80,    branches: 80,    functions: 80  }}

测试报告中达到标准的背景为绿色:

注意:与CI集成时不要设置覆盖率指标,否则若未到达指标,Job会终止。
LCOV
coverageIstanbulReporter中reports参数为[ 'html', 'lcovonly' ],会生成html和lcov两种格式的报告。报告文件lcov.info可与Sonar集成,在Sonar管理界面配置LCOV Files路径,即可在Sonar中查看测试情况。

另外,与Sonar集成时需配置TypeScript Exclusions,排除.spec.ts,否则统计覆盖率时将包含测试文件。

编写测试

第一个测试

使用CLI创建Service、Component等时会自动创建测试文件,我们以创建App时生成的测试文件app.component.spec.ts为例:

import {async, TestBed} from '@angular/core/testing';import {RouterTestingModule} from '@angular/router/testing';import {AppComponent} from './app.component';describe('AppComponent', () => {  beforeEach(async(() => {    TestBed.configureTestingModule({      imports: [        RouterTestingModule      ],      declarations: [        AppComponent      ],    }).compileComponents();  }));  it('should create the app', () => {    const fixture = TestBed.createComponent(AppComponent);    const app = fixture.debugElement.componentInstance;    expect(app).toBeTruthy();  });  it(`should have as title 'hello'`, () => {    const fixture = TestBed.createComponent(AppComponent);    const app = fixture.debugElement.componentInstance;    expect(app.title).toEqual('hello');  });  it('should render title in a h2 tag', () => {    const fixture = TestBed.createComponent(AppComponent);    fixture.detectChanges();    const compiled = fixture.debugElement.nativeElement;    expect(compiled.querySelector('h2').textContent).toContain('Welcome to hello!');  });});

测试结构
从上例我们可以了解测试的主要结构:
describe函数中包含了beforeEach和it两类函数。describe相当于Java测试中的suite,也就是测试组,其中可以包含多个测试用例it。一般一个测试文件含有一个describe,当然也可以有多个。beforeEach相当于Java测试中的@Before方法,每个测试用例执行前调用一次。同样,还有afterEach、beforeAll、afterAll函数,afterEach在每个测试用例执行后调用一次,beforeAll、afterAll相当于Java测试中的@BeforeClass、@AfterClass方法,每个describe执行前后调用一次。

describe和it的第一个参数是测试说明。一个it中可以包含一个或多个expect来执行测试验证。

TestBed
TestBed是Angular测试中最重要的工具。

TestBed.configureTestingModule()方法动态构建TestingModule来模拟Angular @NgModule,支持@NgModule的大多数属性。

测试中需导入测试的组件及依赖。在AppComponent页面中使用了router-outlet,因此我们导入了RouterTestingModule来模拟RouterModule。Test Module预配置了一些元素,比如BrowserModule,不需导入。

TestBed.createComponent()方法创建组件实例,返回ComponentFixture。ComponentFixture是一个测试工具(test harness),用于与创建的组件和相应元素进行交互。

nativeElement和DebugElement
示例中使用了fixture.debugElement.nativeElement,也可以写成fixture.nativeElement。实际上,fixture.nativeElement是fixture.debugElement.nativeElement的一种简化写法。nativeElement依赖于运行时环境,Angular依赖DebugElement抽象来支持跨平台。Angular创建DebugElement tree来包装native element,nativeElement返回平台相关的元素对象。

我们的测试样例仅运行在浏览器中,因此nativeElement总为HTMLElement,可以使用querySelector()、querySelectorAll()方法来查询元素。

element.querySelector('p');element.querySelector('input');element.querySelector('.welcome');element.querySelectorAll('span');

detectChanges
createComponent() 函数不会绑定数据,必须调用fixture.detectChanges()来执行数据绑定,才能在组件元素中取得内容:

it('should render title in a h2 tag', () => {  const fixture = TestBed.createComponent(AppComponent);  fixture.detectChanges();  const compiled = fixture.debugElement.nativeElement;  expect(compiled.querySelector('h2').textContent).toContain('Welcome to hello!');});

当数据模型值改变后,也需调用fixture.detectChanges()方法:

it('should render title in a h2 tag', () => {  const fixture = TestBed.createComponent(AppComponent);  const app = fixture.componentInstance;  app.title = 'china';  fixture.detectChanges();  const compiled = fixture.nativeElement;  expect(compiled.querySelector('h2').textContent).toContain('Welcome to china!');});

可以配置自动检测,增加ComponentFixtureAutoDetect provider:

import { ComponentFixtureAutoDetect } from '@angular/core/testing';...TestBed.configureTestingModule({  providers: [    { provide: ComponentFixtureAutoDetect, useValue: true }  ]});

启用自动检测后仅需在数值改变后调用detectChanges():

it('should display original title', () => {  // Hooray! No `fixture.detectChanges()` needed  expect(h2.textContent).toContain(comp.title);});it('should still see original title after comp.title change', () => {  const oldTitle = comp.title;  comp.title = 'Test Title';  // Displayed title is old because Angular didn't hear the change :(  expect(h2.textContent).toContain(oldTitle);});it('should display updated title after detectChanges', () => {  comp.title = 'Test Title';  fixture.detectChanges(); // detect changes explicitly  expect(h2.textContent).toContain(comp.title);});

同步和异步beforeEach
组件常用 @Component.templateUrl 和 @Component.styleUrls 属性来指定外部模板和CSS,Angular编译器会在编译期间读取外部文件。

@Component({  selector: 'app-banner',  templateUrl: './banner-external.component.html',  styleUrls:  ['./banner-external.component.css']})
beforeEach(() => {  TestBed.configureTestingModule({    declarations: [ BannerComponent ],  });  fixture = TestBed.createComponent(BannerComponent);});

当用CLI的ng test命令运行含有如上同步beforeEach方法的测试时没有问题,因为会在运行测试之前先编译。若在非CLI环境下运行这些测试则可能失败。要解决这个问题,可以调用compileComponents()进行显示的编译。compileComponents()方法是异步的,必须在async()方法中调用:

beforeEach(async(() => {  TestBed.configureTestingModule({    imports: [      RouterTestingModule    ],    declarations: [      AppComponent    ],  }).compileComponents();}));

调用compileComponents()会关闭当前的TestBed实例,不再允许进行配置,不能再调用任何TestBed中的配置方法,既不能调 configureTestingModule(),也不能调用任何 override... 方法。

常同时使用同步beforeEach和异步beforeEach来协同工作,异步的 beforeEach() 负责编译组件,同步的beforeEach()负责执行其余的准备代码。测试运行器会先调用异步beforeEach方法,运行完毕后再调用同步方法。

重构
示例中重复代码较多,我们用两个beforeEach来简化一下:

import {async, ComponentFixture, TestBed} from '@angular/core/testing';import {RouterTestingModule} from '@angular/router/testing';import {AppComponent} from './app.component';describe('AppComponent', () => {  let fixture: ComponentFixture;  let app: AppComponent;  beforeEach(async(() => {    TestBed.configureTestingModule({      imports: [        RouterTestingModule      ],      declarations: [        AppComponent      ],    }).compileComponents();  }));  beforeEach(() => {    fixture = TestBed.createComponent(AppComponent);    app = fixture.componentInstance;    fixture.detectChanges();  });  it('should create the app', () => {    expect(app).toBeTruthy();  });  it(`should have as title 'hello'`, () => {    expect(app.title).toEqual('hello');  });  it('should render title in a h2 tag', () => {    const compiled = fixture.nativeElement;    expect(compiled.querySelector('h2').textContent).toContain('Welcome to hello!');  });});

也可以把这两个 beforeEach() 重构成一个异步的beforeEach():

beforeEach(async(() => {  TestBed.configureTestingModule({     imports: [        RouterTestingModule      ],      declarations: [        AppComponent      ],  })  .compileComponents()  .then(() => {    fixture = TestBed.createComponent(AppComponent);    app = fixture.componentInstance;    fixture.detectChanges();  });}));

依赖注入与Mock

对简单对象进行测试可以用new创建实例:

describe('ValueService', () => {  let service: ValueService;  beforeEach(() => { service = new ValueService(); });    ...});

不过大多数Service、Component等有多个依赖项,使用new很不方便。若用DI来创建测试对象,当依赖其他服务时,DI会找到或创建依赖的服务。要测试某个对象,在configureTestingModule中配置测试对象本身及依赖项,然后调用TestBed.get()注入测试对象:

beforeEach(() => {  TestBed.configureTestingModule({ providers: [ValueService] });  service = TestBed.get(ValueService);});

单元测试的原则之一:仅对要测试对象本身进行测试,而不对其依赖项进行测试,依赖项通过mock方式注入,而不使用实际的对象,否则测试不可控。

Mock优先使用Spy方式:

let masterService: MasterService;beforeEach(() => {  const spy = jasmine.createSpyObj('ValueService', ['getValue']);    spy.getValue.and.returnValue('stub value');  TestBed.configureTestingModule({    // Provide both the service-to-test and its (spy) dependency    providers: [      MasterService,      { provide: ValueService, useValue: spy }    ]  });  masterService = TestBed.get(MasterService);});

HttpClient、Router、Location

同测试含其它依赖的对象一样,可以mock HttpClient、Router、Location:

beforeEach(() => {  const httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);  TestBed.configureTestingModule({    providers: [      {provide: HttpClient, useValue: httpClientSpy}    ]  });});
beforeEach(async(() => {  const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);  const locationSpy = jasmine.createSpyObj('Location', ['back']);  TestBed.configureTestingModule({    providers: [      {provide: Router, useValue: routerSpy},      {provide: Location, useValue: locationSpy}    ]  })    .compileComponents();}));

Component测试

  • 仅测试组件类

测试组件类就像测试服务那样简单:
组件类

export class WelcomeComponent  implements OnInit {  welcome: string;  constructor(private userService: UserService) { }  ngOnInit(): void {    this.welcome = this.userService.isLoggedIn ?      'Welcome, ' + this.userService.user.name : 'Please log in.';  }}

Mock类

class MockUserService {  isLoggedIn = true;  user = { name: 'Test User'};};

测试

...beforeEach(() => {  TestBed.configureTestingModule({    // provide the component-under-test and dependent service    providers: [      WelcomeComponent,      { provide: UserService, useClass: MockUserService }    ]  });  // inject both the component and the dependent service.  comp = TestBed.get(WelcomeComponent);  userService = TestBed.get(UserService);});...it('should ask user to log in if not logged in after ngOnInit', () => {  userService.isLoggedIn = false;  comp.ngOnInit();  expect(comp.welcome).not.toContain(userService.user.name);  expect(comp.welcome).toContain('log in');});
  • 组件DOM测试

只涉及类的测试可以判断组件类的行为是否正常,但不能确定组件是否能正常渲染和交互。
进行组件DOM测试,需要使用TestBed.createComponent()等方法,第一个测试即为组件DOM测试。

TestBed.configureTestingModule({  declarations: [ BannerComponent ]});const fixture = TestBed.createComponent(BannerComponent);const component = fixture.componentInstance;expect(component).toBeDefined();

dispatchEvent
为模拟用户输入,比如为input元素输入值,要找到input元素并设置它的 value 属性。Angular不知道你设置了input元素的value属性,需要调用 dispatchEvent() 触发输入框的 input 事件,再调用 detectChanges():

it('should convert hero name to Title Case', () => {  // get the name's input and display elements from the DOM  const hostElement = fixture.nativeElement;  const nameInput: HTMLInputElement = hostElement.querySelector('input');  const nameDisplay: HTMLElement = hostElement.querySelector('span');  nameInput.value = 'quick BROWN  fOx';  // dispatch a DOM event so that Angular learns of input value change.  nameInput.dispatchEvent(newEvent('input'));  fixture.detectChanges();  expect(nameDisplay.textContent).toBe('Quick Brown  Fox');});

嵌套组件

组件中常常使用其他组件:

对于无害的内嵌组件可以直接将其添加到declarations中,这是最简单的方式:

describe('AppComponent & TestModule', () => {  beforeEach(async(() => {    TestBed.configureTestingModule({      declarations: [        AppComponent,        BannerComponent,        WelcomeComponent      ]    })    .compileComponents().then(() => {      fixture = TestBed.createComponent(AppComponent);      comp    = fixture.componentInstance;    });  }));  ...});

也可为无关紧要的组件创建一些测试桩:

@Component({selector: 'app-banner', template: ''})class BannerStubComponent {}@Component({selector: 'router-outlet', template: ''})class RouterOutletStubComponent { }@Component({selector: 'app-welcome', template: ''})class WelcomeStubComponent {}

然后在TestBed的配置中声明它们:

TestBed.configureTestingModule({  declarations: [    AppComponent,    BannerStubComponent,    RouterOutletStubComponent,    WelcomeStubComponent  ]})

另一种办法是使用NO_ERRORS_SCHEMA,要求 Angular编译器忽略那些不认识的元素和属性:

TestBed.configureTestingModule({  declarations: [    AppComponent,    RouterLinkDirectiveStub  ],  schemas: [ NO_ERRORS_SCHEMA ]})

NO_ERRORS_SCHEMA方法比较简单,但不要过度使用。NO_ERRORS_SCHEMA 会阻止编译器因疏忽或拼写错误而缺失的组件和属性,如人工找出这些 bug会很费时。
RouterLinkDirectiveStub

import { Directive, Input, HostListener } from '@angular/core';@Directive({  selector: '[routerLink]'})export class RouterLinkDirectiveStub {  @Input('routerLink') linkParams: any;  navigatedTo: any = null;  @HostListener('click')  onClick() {    this.navigatedTo = this.linkParams;  }}

属性指令测试

import { Directive, ElementRef, Input, OnChanges } from '@angular/core';@Directive({ selector: '[highlight]' })/** Set backgroundColor for the attached element to highlight color and set the element's customProperty to true */export class HighlightDirective implements OnChanges {  defaultColor =  'rgb(211, 211, 211)'; // lightgray  @Input('highlight') bgColor: string;  constructor(private el: ElementRef) {    el.nativeElement.style.customProperty = true;  }  ngOnChanges() {    this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;  }}

属性型指令肯定要操纵 DOM,如只针对类测试不能证明指令的有效性。若通过组件来测试,单一的用例一般无法探索指令的全部能力。因此,更好的方法是创建一个能展示该指令所有用法的人造测试组件:

@Component({  template: `  

Something Yellow

The Default (Gray)

No Highlight

`})class TestComponent { }

测试程序:

beforeEach(() => {  fixture = TestBed.configureTestingModule({    declarations: [ HighlightDirective, TestComponent ]  })  .createComponent(TestComponent);  fixture.detectChanges(); // initial binding  // all elements with an attached HighlightDirective  des = fixture.debugElement.queryAll(By.directive(HighlightDirective));  // the h3 without the HighlightDirective  bareH2 = fixture.debugElement.query(By.css('h3:not([highlight])'));});// color testsit('should have three highlighted elements', () => {  expect(des.length).toBe(3);});it('should color 1st 

background "yellow"', () => { const bgColor = des[0].nativeElement.style.backgroundColor; expect(bgColor).toBe('yellow');});it('should color 2nd

background w/ default color', () => { const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; const bgColor = des[1].nativeElement.style.backgroundColor; expect(bgColor).toBe(dir.defaultColor);});it('should bind background to value color', () => { // easier to work with nativeElement const input = des[2].nativeElement as HTMLInputElement; expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor'); // dispatch a DOM event so that Angular responds to the input value change. input.value = 'green'; input.dispatchEvent(newEvent('input')); fixture.detectChanges(); expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor');});it('bare

should not have a customProperty', () => { expect(bareH2.properties['customProperty']).toBeUndefined();});

Pipe测试

describe('TitleCasePipe', () => {  // This pipe is a pure, stateless function so no need for BeforeEach  let pipe = new TitleCasePipe();  it('transforms "abc" to "Abc"', () => {    expect(pipe.transform('abc')).toBe('Abc');  });  it('transforms "abc def" to "Abc Def"', () => {    expect(pipe.transform('abc def')).toBe('Abc Def');  });  ...});

Testing Module

RouterTestingModule
在前面的测试中我们使用了测试桩RouterOutletStubComponent,与Router有关的测试还可以使用RouterTestingModule:

beforeEach(async(() => {  TestBed.configureTestingModule({    imports: [      RouterTestingModule    ],    declarations: [      AppComponent    ],  }).compileComponents();}));

RouterTestingModule还可以模拟路由:

beforeEach(() => {  TestBed.configureTestModule({    imports: [      RouterTestingModule.withRoutes(        [{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}]      )    ]  });});

HttpClientTestingModule

describe('HttpClient testing', () => {  let httpClient: HttpClient;  let httpTestingController: HttpTestingController;  beforeEach(() => {    TestBed.configureTestingModule({      imports: [ HttpClientTestingModule ]    });    // Inject the http service and test controller for each test    httpClient = TestBed.get(HttpClient);    httpTestingController = TestBed.get(HttpTestingController);  });  afterEach(() => {    // After every test, assert that there are no more pending requests.    httpTestingController.verify();  });  it('can test HttpClient.get', () => {    const testData: Data = {name: 'Test Data'};    // Make an HTTP GET request    httpClient.get(testUrl)      .subscribe(data =>        // When observable resolves, result should match test data        expect(data).toEqual(testData)      );    // The following `expectOne()` will match the request's URL.    // If no requests or multiple requests matched that URL    // `expectOne()` would throw.    const req = httpTestingController.expectOne('/data');    // Assert that the request is a GET.    expect(req.request.method).toEqual('GET');    // Respond with mock data, causing Observable to resolve.    // Subscribe callback asserts that correct data was returned.    req.flush(testData);    // Finally, assert that there are no outstanding requests.    httpTestingController.verify();  });    ...});

调试

在浏览器测试结果页面,点击"DEBUG"按钮会打开新浏标签页并重新运行测试程序。按"F12"打开调试界面,然后进入Sources找到测试文件(CTRL+P),在测试程序中设置断点即可调试。

配置E2E测试

E2E测试使用Jasmine和Protractor测试框架,Protractor是Angular端到端测试框架。

安装Protractor

npm i -g protractor

安装后,node_modules\protractor\bin目录含有两个命令行工具protractor和webdriver-manager,其中webdriver-manager负责管理驱动、启停Selenium Server。

webdriver-manager命令:

clean      removes all downloaded driver files from the out_dirstart      start up the selenium servershutdown   shut down the selenium serverstatus     list the current available driversupdate     update selected binariesversion    get the current version

更新驱动:

webdriver-manager update

默认安装chromedriver、geckodriver和selenium standalone,驱动目录为node_modules\protractor\node_modules\webdriver-manager\selenium,下载使用的url配置在webdriver-manager\config.json文件内:

"cdnUrls": {  "selenium": "https://selenium-release.storage.googleapis.com/",  "chromedriver": "https://chromedriver.storage.googleapis.com/",  "geckodriver": "https://github.com/mozilla/geckodriver/releases/download/",  "iedriver": "https://selenium-release.storage.googleapis.com/",  "androidsdk": "http://dl.google.com/android/"}

可以修改为其它CDN:

"cdnUrls": {  "selenium": "https://mirrors.huaweicloud.com/selenium/",  "chromedriver": "https://mirrors.huaweicloud.com/chromedriver/",  "geckodriver": "https://mirrors.huaweicloud.com/geckodriver/",  "iedriver": "https://selenium-release.storage.googleapis.com/",  "androidsdk": "http://dl.google.com/android/"}

也可以使用参数--alternate_cdn:

webdriver-manager update --alternate_cdn=...

配置文件

使用CLI创建的App会生成一个e2e项目,其中包含测试配置protractor.conf.js及测试代码。
protractor.conf.js

const { SpecReporter } = require('jasmine-spec-reporter');exports.config = {  allScriptsTimeout: 11000,  specs: [    './src/**/*.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: require('path').join(__dirname, './tsconfig.e2e.json')    });    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));  }};

默认,Protractor使用Jasmine测试框架,使用直连方式连接Chrome浏览器,测试文件扩展名为.e2e-spec.ts。

浏览器配置

Protractor支持Chrome、Firefox、Safari、IE等浏览器。
多浏览器
Protractor支持同时启动多个浏览器,一个浏览器时,在配置中使用capabilities选项;多个浏览器时,使用multiCapabilities:

multiCapabilities: [{  browserName: 'firefox'}, {  browserName: 'chrome'}]

另外需在package.json中增加配置:

"scripts": {  "webdriver-update": "webdriver-manager update"}

在运行测试前更新浏览器驱动:

npm run webdriver-update

否则项目中的驱动不会更新(默认只有chrome驱动,运行webdriver-manager update仅更新全局的驱动),运行测试会报如下错误:

No update-config.json found. Run 'webdriver-manager update' to download binaries

浏览器选项

capabilities: {  'browserName': 'chrome',  'chromeOptions': {    'args': ['show-fps-counter=true']  }},
capabilities: {  'browserName': 'firefox',  'moz:firefoxOptions': {    'args': ['--safe-mode']  }},

更多选项请查看相应驱动ChromeDriver、GeckoDriver。

Selenium Server配置

使用Standalone Selenium Server时,需安装JDK。
更新driver后启动Selenium Server:

webdriver-manager updatewebdriver-manager start

删除原配置中的directConnect、baseUrl:

directConnect: true,baseUrl: 'http://localhost:4200/',

增加seleniumAddress(默认为http://localhost:4444/wd/hub):

seleniumAddress: 'http://localhost:4444/wd/hub',

运行测试

运行E2E测试:

ng e2e

常用参数:

--base-url  Base URL for protractor to connect to.--configuration (-c)  A named configuration environment, as specified in the "configurations" section of angular.json.--host  Host to listen on.--port  The port to use to serve the application.--prod  When true, sets the build configuration to the production environment.--protractor-config  The name of the Protractor configuration file.--webdriver-update  Try to update webdriver.

driver安装好后,若未更新浏览器,不必每次都更新driver:

ng e2e --webdriver-update=false

如运行测试时报如下错误:

events.js:167      throw er; // Unhandled 'error' event      ^Error: read ECONNRESET    at TLSWrap.onStreamRead (internal/stream_base_commons.js:111:27)Emitted 'error' event

可尝试更新package后再测试:

npm i npm@latest -gnpm update

指定配置文件

不同的环境若配置不同,可使用不同的配置文件。

比如,在CI环境中启用Chrome Headless模式:
在e2e根目录下创建一名为protractor-ci.conf.js的新文件,内容如下:

const config = require('./protractor.conf').config;config.capabilities = {  browserName: 'chrome',  chromeOptions: {    args: ['--headless', '--no-sandbox']  }};exports.config = config;

注意: windows系统要增加参数--disable-gpu

运行以下命令测试:

ng e2e --protractor-config=e2e\protractor-ci.conf.js --webdriver-update=false

编写E2E测试

第一个测试

import { AppPage } from './app.po';describe('workspace-project App', () => {  let page: AppPage;  beforeEach(() => {    page = new AppPage();  });  it('should display welcome message', () => {    page.navigateTo();    expect(page.getTitleText()).toEqual('Welcome to hello!');  });});
import { browser, by, element } from 'protractor';export class AppPage {  navigateTo() {    return browser.get('/');  }  getTitleText() {    return element(by.css('app-root h2')).getText();  }}

E2E测试与单元测试都使用了Jasmine,测试结构相同。Protractor提供了全局的browser、element、by,分别用来打开页面和查找元素。

Protractor

describe('Protractor Demo App', function() {  it('should add one and two', function() {    browser.get('http://juliemr.github.io/protractor-demo/');    element(by.model('first')).sendKeys(1);    element(by.model('second')).sendKeys(2);    element(by.id('gobutton')).click();    expect(element(by.binding('latest')).getText()).        toEqual('5'); // This is wrong!  });});
  • by.model('first') 查找元素ng-model="first"
  • by.id('gobutton') 根据id查找元素
  • by.binding('latest') 查找绑定变量的元素 {{latest}}

2018上海马拉松

参考资料

Angular Testing
Jasmine Behavior-Driven JavaScript
Karma
Protractor - end-to-end testing for Angular

测试 配置 文件 浏览器 浏览 组件 运行 方法 元素 报告 环境 单元 对象 属性 驱动 覆盖率 支持 更新 编译 命令 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 小程序商城软件开发系统 第三次科技改革互联网 虚拟机连接本机的数据库 数据中心服务器虚拟化 银行数据库录入员 鼎捷数据库表名参照表 玩的游戏的服务器忘记了怎么办 nx服务器未连接10004 数据库资料下载 致配互联网科技有限公司 香港 云服务器管理 我的世界服务器有人却连接不到服务器 华为无法联系到服务器怎么回事 网络安全知识三百字 没有域名可以搭建游戏服务器吗 公安网络安全图片大全 我的世界怎么打开神奇宝贝服务器 计算机网络技术毕业生简历 计算机网络技术里有什么专业 软件开发培训哪个牌子质量好 scott数据库 我国著名的科技报告数据库有 学习网络安全后的心得 公司服务器ip 吴中区电子网络技术怎么样 软件开发精益管理工具 服务器超时收费标准 软件开发要求英语四六级吗 济南品众网络技术有限公司 应聘网络技术员会有什么问题
0