【问题标题】:Dockerized Angular E2E tests pass on local machine, but timeout in Travis CIDockerized Angular E2E 测试在本地机器上通过,但在 Travis CI 中超时
【发布时间】:2019-07-24 19:04:07
【问题描述】:

我试图将我的角度 e2e 测试 dockerize。简单来说,我创建了一个 docker-compose.test.yml 文件:

version: "3.4"

services:
  # set up angular app
  angular:
    build: 
      context: ./frontend
      dockerfile: sample.Dockerfile
      target: test
    image: angular:test
    container_name: angular
    ports: 
      - "4200:4200"
    depends_on: 
      - nodejs 
    command: npm run e2e -- --protractor-config=e2e/protractor-docker.conf.js --host 0.0.0.0

  nodejs:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: development
    image: nodejs:dev
    container_name: nodejs
    ports: 
      - "3000:3000"
    networks:
      - backend
    depends_on: 
      - db
    restart: always
  # the mongo database
  db:
    image: mongo
    container_name: db
    networks: 
      - backend
    ports: 
      - "27017:27017"
networks: 
  backend: 
    driver: bridge

对于量角器配置,我按照此链接中的教程进行操作:

Angular Tutorial for Travis CI

我不得不做一些调整,例如:

let config = require('./protractor.conf').config;

// Tell protrator where the chrome driver is
// https://gitlab.com/dasch8/angular-ci/
// https://hub.docker.com/r/weboaks/node-karma-protractor-chrome/
config.chromeDriver = "/usr/bin/chromedriver";

config.allScriptsTimeout = 60000;

config.getPageTimeout = 60000;

config.jasmineNodeOpts.defaultTimeoutInterval = 60000;

// have it connect to the angular app
// config.baseUrl = "http://angular:4200";

config.capabilities = {
  browserName: 'chrome',
  chromeOptions: {
    args: ['--headless', '--no-sandbox', '--disable-gpu']
  },

};


exports.config = config;

使用配置,e2e 测试在我的本地机器上成功。但是,当我在 .travis.yml 的脚本中运行它时:

before_script: 
    - docker-compose -f docker-compose.test.yml build
  script:
    - docker-compose -f docker-compose.test.yml run --name angular -p 4200:4200 angular

Travis CI 失败。输出为:

 - Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
  - Failed: Timed out waiting for asynchronous Angular tasks to finish after 60 seconds. This may be because the current page is not an Angular application. Please see the FAQ for more details: https://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular

这很奇怪,因为我写道:

import { AppPage } from './app.po';

// Test bed
describe('workspace-project App', () => {
  let page: AppPage;

  beforeEach(() => {
    page = new AppPage();
  });

  it('should display navbar with title',  () => {
    page.navigateTo().then(() => {
      console.log('Successfully connected to the page');
    });

    expect(page.getHeaderText()).toEqual('hobbies');

  });
});

在 Travis CI 的日志中,日志会打印出来:

成功连接到页面。其余日志可在此处找到:

My Travis CI logs

我不确定我做错了什么。测试在我的本地机器上通过真是太奇怪了。我尝试通过使用 selenium/hub 和 selenium/node-chrome 图像以不同的方式解决此问题,并使量角器连接到该 selenium 服务器。这是同一个故事。我让它在我的本地机器上工作,但它在 TravisCI 中失败了。我将在另一个问题中询问硒问题。

【问题讨论】:

  • 你的意思是端到端测试在容器中在你的机器上是成功的,还是你直接运行它们?当你在本地使用这些 docker-compose 命令时会发生什么?
  • 容器中的端到端测试在我的机器上是成功的。当我在本地运行 docker-compose 命令时,测试通过了。 @jonrsharpe
  • 你好。我将所有超时值(config.allScriptsTimeout、config.getPageTimeout、config.jasmineNodeOpts.defaultTimeoutInterval)增加到 180000 毫秒。这样做使我的测试通过了 Travis CI。但是,如果我遇到需要更长时间的测试怎么办?我是否必须猜测适当的超时时间才能确保我的测试在 Travis CI 中通过?
  • 我建议仔细研究失败的确切位置并在此之前添加动态等待。它在您的测试中是否始终在同一点失败?
  • @C.Peck 是的,它一直在同一点失败。我也在考虑添加等待。

标签: angular selenium docker protractor travis-ci


【解决方案1】:

终于解决了!!!所以,我找到了这个链接,我得到了一些灵​​感: Chrome Not Reachable in Docker Network

幸运的是,个人使用的配置几乎相同,但有趣的是个人正在反向代理到他们的 Web 应用程序。

我也是这样做的:

#default.conf for proxy service

upstream docker-web {

    #docker angular container
    server angular:4200; 
}

upstream docker-node {

    #docker nodejs container
    server nodejs:3000;
}

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://docker-web;
        #proxy_redirect off; 
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
        add_header         X-Upstream $upstream_addr;
    }

    location /api {
        proxy_pass http://docker-node;
        proxy_redirect off; 
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
        add_header         X-Upstream $upstream_addr;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

对于我在 nginx 上托管的 Angular Web 应用程序:

server {
    listen       4200;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }



    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

我使用我的角度图像为量角器创建了一个单独的容器,我将提供:

###################
##    builder    ##
###################

FROM  node:10-alpine AS builder
RUN  mkdir -p /usr/src/frontend
WORKDIR /usr/src/frontend
COPY package.json /usr/src/frontend
RUN npm cache clean --force \
  && npm install
COPY . /usr/src/frontend
RUN npm run build -- --prod


##################
##  production  ##
##################

FROM nginx:alpine AS production
COPY --from=builder /usr/src/frontend/dist/frontend /usr/share/nginx/html
COPY --from=builder /usr/src/frontend/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

#################
## development ##
#################

# because development uses the same stuff from builder image
FROM builder AS development
EXPOSE 4200
CMD ["npm", "start"]


##################
##     test     ##
##################


# because test uses the same stuff from builder image
FROM builder AS test
RUN apk update && \
    apk upgrade && \
    apk add --no-cache chromium nss chromium-chromedriver
EXPOSE 9876
ENV CHROME_BIN /usr/bin/chromium-browser
# just needed the chromedriver!!!
# https://github.com/angular/angular-cli/issues/5019
# https://gitlab.com/dasch8/angular-ci/blob/master/README.md
CMD [ "npm", "run", "test", "--", "--no-watch", "--no-progress", "--browsers=ChromeHeadlessCI"]


FROM production

然后我也创建了一个 selenium 网格服务和 chrome-node 服务。 Docker Selenium 的文档可在此处找到:

Docker Selenium

我的错误是主机名(docker-compose 文件中的容器/服务名称)没有正确解析,因为它们不在同一个网络上。由于量角器连接到 selenium,并且 selenium 处理 E2E 测试,browser.get('http://proxy') 找不到 angular 应用程序,因为 Selenium 与代理容器不在同一个网络上。然后我删除了我在 docker-compose 文件中使用的所有网络,并创建了一个新的 docker-compose 文件,其中包括我的 selenium 网格服务(称为 selenium-hub)、selenium/node-chrome 服务(称为 chrome_node)、MEAN 堆栈(角度、nodejs、db)和量角器服务(通过从 docker 文件定位测试构建)。

我的 docker-compose.selenium.yml 文件:

version: '3.4'

services:
  selenium-hub:
    image: selenium/hub:3.141.59
    container_name: selenium-hub
    environment: 
      - GRID_TIMEOUT=10000
    ports: 
      - "4444:4444"
    privileged: true
    #tty: true

  chrome_node:
    image: selenium/node-chrome:3.141.59
    volumes: 
      - /dev/shm:/dev/shm
    container_name: chrome_node
    environment:
      - HUB_PORT_4444_TCP_ADDR=selenium-hub
      - HUB_PORT_4444_TCP_PORT=4444
    ports:
      - "5555:5555"
    privileged: true
    depends_on:
      - selenium-hub
    #tty: true
  proxy: 
    build: 
      context: ./proxy
      dockerfile: Dockerfile
    image: proxy
    container_name: proxy
    # HOST:CONTAINER
    ports: 
      - "80:80"
    #tty: true
    depends_on: 
      - angular
  angular:
    build:
      context: ./frontend
      dockerfile: sample.Dockerfile
      target: production
    image: angular:prod
    container_name: angular
    ports:
      - "4200:4200"
    privileged: true 
    depends_on: 
        - nodejs
    #tty: true
  nodejs:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: development
    image: nodejs:dev
    container_name: nodejs
    ports: 
      - "3000:3000"
    depends_on: 
      - db
    restart: always
    #tty: true
  # the mongo database
  db:
    image: mongo
    container_name: db
    ports: 
      - "27017:27017"
    #tty: true
  protractor:
    build:
      context: ./frontend
      dockerfile: sample.Dockerfile
      target: test
    image: angular:test
    container_name: protractor
    ports: 
      - "9876:9876"
    privileged: true
    depends_on:
      - selenium-hub
      - chrome_node
    #tty: true --> not neccesary
    command: npm run e2e -- --protractor-config=e2e/protractor-ci.conf.js --webdriver-update=false --dev-server-target= 

这是我的新量角器配置文件:

let config = require('./protractor.conf').config;


// do not use the chromeDriver. If you do, it will not connect to Selenium service
config.directConnect = false;


// have it connect to the angular app
config.baseUrl = "http://proxy";

// Protractor getting the page-timeout
config.getPageTimeout = 60000;

// Selenium Webdriver timeout
config.allScriptsTimeout = 60000;

// Jasmine test script timeout
config.jasmineNodeOpts.defaultTimeoutInterval = 60000;

config.seleniumAddress = 'http://selenium-hub:4444/wd/hub'

config.capabilities = {
  browserName: 'chrome',
  chromeOptions: {
    args: ['--headless', '--disable-gpu', '--no-sandbox'],
  },
};

标志--dev-server-target= 让我们知道不要启动 ng serve(因为我们正在 nginx 中启动我们的 Angular 应用程序)。这样做之后,我的测试通过了!!!而且,我将超时设置为 60000 并且测试在 3 秒内通过,这是一个很大的进步!!!! :)

我在 .travis.yml 中使用的脚本:

before_script: 
        - docker-compose -f docker-compose.selenium.yml build
script:
        - docker-compose -f docker-compose.selenium.yml up -d chrome_node proxy
        - docker container ls
        - docker-compose -f docker-compose.selenium.yml up protractor

对于网络,docker-compose up 命令自行处理,Docker 的 DNS 解析器将解析主机名(我们在 docker-compose 文件中的容器/服务名称)。有关 docker compose 网络的更多信息,请参阅 Docker 文档:Docker Compose and Networks

这是构建的日志:

Travis CI logs

我希望这对将来的任何人都有帮助:)

【讨论】:

    【解决方案2】:

    那么,如下添加等待会影响什么吗?

    import { AppPage } from './app.po';
    
    // Test bed
    describe('workspace-project App', () => {
      let page: AppPage;
    
      beforeEach(() => {
        page = new AppPage();
      });
    
      it('should display navbar with title',  () => {
        page.navigateTo().then(() => {
          console.log('Successfully connected to the page');
        });
    
    browser.ignoreSynchronization = true
    
    driver.wait(function () {
    return driver.isElementPresent(page.getHeaderText('hobbies'));
    }, timeout);
    
        expect(page.getHeaderText()).toEqual('hobbies');
    
        browser.ignoreSynchronization = false;
    
      });
    });
    

    【讨论】:

    • 我按照你的建议做了,但仍然出现超时错误。 travis-ci.com/manofthelionarmy/ci_cd_project/jobs/182202345 这是我按照您的建议添加的代码的 github 存储库:github.com/manofthelionarmy/ci_cd_project/blob/…@C.Peck
    • 我会再尝试一件事:在操作之前关闭waitforAngular(),然后让它重新打开,注意browser.ignoreSychronization 行。有什么不同的结果吗?
    • 我已经克隆了你的项目,但是要让它继续运行还有很多设置工作要做。
    • 我建议删除 .travis.yml 中的加密行。 Travis 将停止,因为我使用 travis @C.Peck 加密了一些文件
    • 我现在遇到以下错误,我相信我已经安装了所有东西:Error: TSError: ⨯ Unable to compile TypeScript: error TS2688: Cannot find type definition file for 'jasmine'. error TS2688: Cannot find type definition file for 'jasminewd2'. error TS2688: Cannot find type definition file for 'node'.
    【解决方案3】:

    我遵循 C.Peck 的回答。我在本地机器上进行了测试,它可以工作;但是,它仍然无法在 TravisCI 中工作,如我的日志所示:

    TravisCI logs

    我什至将等待超时设置为 11 秒,但测试仍然失败。然后我将protractor-docker.conf.js中的allScriptsTimeout增加到180000。这样做之后,测试通过了:

    My Travis CI logs of the Successful Angular E2E Test

    但是,测试需要 2 分 10 秒才能完成。这令人沮丧,因为我只是检查标题是否出现在导航栏中。恐怕如果我测试诸如表单和单击按钮之类的东西,Travis CI 甚至可能需要更长的时间。

    【讨论】:

    • 正如我所担心的,当我尝试测试我的表单时,测试会超时。它在我的本地机器上工作,但在 Travis CI 上超时
    • 这不应该是必要的,那是一个荒谬的长时间必须一直等待。我希望我能告诉你发生了什么,但这里显然有问题。
    • 我的感觉完全一样,这些测试几乎让我把头发扯掉。 @C.Peck
    • 我想我找到了可能的罪魁祸首。在 environment.ts 中,它包含 NodeJs 后端的 url。但是,该 url 包含本地机器上的 docker-machine 的 IP 地址。该 IP 地址与 TravisCI 中的不同。我尝试将其替换为:http://nodejs:3000,因为我来自 docker-compose 的 nodejs 服务被命名为 nodejs
    • 这也可以解释为什么我的测试停滞不前。当 waitForAngularEnable(true) 时,它将等到 http 调用完成。由于在 onNgOnit 中我正在发出 get 请求,它会导致 ERR_CONNECTION_REFUSED,从而使测试停止。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2012-12-17
    相关资源
    最近更新 更多