continuous-integrationautomated-testsgithub-actionsplaywrightcucumberjs

How to access localhost from a step in GitHub actions?


I have 2 repo, one is for my NextJS app let's call it repo A. Another one for my testing suite that uses cucumber and playwright, let's call it repo B.

I want to run a Github Actions workflow, whenever there is a pull request to the Main branch of the repo A. In the workflow, the following should happen:

  1. Check out the code from the head of the PR branch from repo A.
  2. Install all the dependencies of repo A.
  3. Build the app and start the app.
  4. Check out the testing suite code from repo B.
  5. Install all the dependencies.
  6. Run the test against the app running from step 3)

So far, I have tried a bunch of different approaches to achieve the expected behavior. The following is my current YAML file for the workflow.

name: Automated Testing workflow
on:
  pull_request: 
    types: 
      - opened
    branches: 
      - 'github-actions/Test'
  workflow_dispatch:
  
jobs:
  build-and-test:
    name: Build and test app
    runs-on: ubuntu-latest
    env:
      APP_URL: ${{ secrets.APP_URL }}
      USERNAME: ${{ secrets.USERNAME }}
      PASSWORD: ${{ secrets.PASSWORD }}
      ORGANISATION_NAME: ${{ secrets.ORGANISATION_NAME }}
    steps:
      - name: Checkout Git repository A
        uses: actions/checkout@v4
        with:
          path: 'app'

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
        
      - name: Install Node.js dependencies
        working-directory: ${{ github.workspace }}/app
        run: npm ci

      - name: Run application
        working-directory: ${{ github.workspace }}/app
        run: npm run start &

      - name: Checkout Automated Testing suite repo
        uses: actions/checkout@v4
        with:
          repository: orgname/testing-repo-B
          token: ${{ secrets.TOKEN }}
          path: 'tests'
      
      - name: Install dependencies
        working-directory: ${{ github.workspace }}/tests
        run: npm ci

      - name: Install playwright browsers
        working-directory: ${{ github.workspace }}/tests
        run: npx playwright install --with-deps

      - name: Run Testing Suite
        run: export APP_URL=$APP_URL &&
          export USERNAME=$USERNAME &&
          export PASSWORD=$PASSWORD &&
          export ORGANISATION_NAME=$ORGANISATION_NAME &&
          npm test
        working-directory: ${{ github.workspace }}/tests

I can see the app from repo A getting build and running as expected. In the logs it does say that the application is running on http://localhost:3000. However, in the step where I'm running the test, I'm getting the following error:

Error: a BeforeAll hook errored, process exiting: file:/home/runner/work/******/******/tests/hooks/hooks.js:36
    at Runtime.runTestRunHooks (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/runtime/run_test_run_hooks.js:22:23)
    at async Runtime.start (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/runtime/index.js:62:9)
    at async runCucumber (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/api/run_cucumber.js:110:21)
    at async Cli.run (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/cli/index.js:56:29)
    at async Object.run [as default] (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/cli/run.js:29:18) {
  [cause]: page.goto: net::ERR_CONNECTION_REFUSED at http://***
  Call log:
    - navigating to "***", waiting until "load"
  
      at Object.<anonymous> (/home/runner/work/******/******/tests/hooks/hooks.js:54:16)
      at async wrapPromiseWithTimeout (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/time.js:57:12)
      at async Object.run (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/user_code_runner.js:64:22)
      at async Runtime.runTestRunHooks (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/runtime/run_test_run_hooks.js:14:31)
      at async Runtime.start (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/runtime/index.js:62:9)
      at async runCucumber (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/api/run_cucumber.js:110:21)
      at async Cli.run (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/cli/index.js:56:29)
      at async Object.run (/home/runner/work/******/******/tests/node_modules/@cucumber/cucumber/lib/cli/run.js:29:18) {
    name: 'Error'
  }
}

If we take a look at the error message page.goto: net::ERR_CONNECTION_REFUSED at http://*** , to me it seems like there is some networking issue. I can see the app running in the GH UI and in the logs I can also see the URL of the app as stated earlier. But somehow in the last step, when trying to run the test and when the automated test tries to go to localhost, I'm getting the ERR_CONNECTION_REFUSED error. And since it's printing the URL as at http://*** I'm assuming that the environment variables have been set correctly from the github secrets.

Can someone please help me fix this issue?

TLDR; can't access the app running on localhost for testing in the Github actions.

I have tried running the app locally and running the test suite against it. The tests run smoothly in the local environment.

I have tried changing the APP_URL from http://localhost:3000 to 127.0.0.1 but no luck.

I have tried a bunch of different configurations of the workflow. I have also tried running the app in a service. The following is a different YAML file of the workflow that I've tried.

name: Automated Testing workflow
on:
  pull_request: 
    types: 
      - opened
    branches: 
      - 'github-actions/Test'
  workflow_dispatch:
  
jobs:
  build-and-test:
    name: Build and test app
    runs-on: ubuntu-latest
    env:
      APP_URL: ${{ secrets.APP_URL}}
      USERNAME: ${{ secrets.USERNAME}}
      PASSWORD: ${{ secrets.PASSWORD}}
      ORGANISATION_NAME: ${{ secrets.ORGANISATION_NAME}}
    container: ubuntu
    services:
      appA:
        image: node:20-alpine
        ports: 
          - 3000:3000
    steps:
      - name: Checkout Git repository
        uses: actions/checkout@v4
        with:
          path: 'app'

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
        
      - name: Install Node.js dependencies
        working-directory: ${{ github.workspace }}/app
        run: npm ci

      - name: Run application
        working-directory: ${{ github.workspace }}/app
        run: npm run start:prod &

      - name: Checkout Automated Testing suite repo
        uses: actions/checkout@v4
        with:
          repository: orgname/testing-repo-B
          token: ${{ secrets.TOKEN }}
          path: 'tests'
      
      - name: Install dependencies
        working-directory: ${{ github.workspace }}/tests
        run: npm ci

      - name: Install playwright browsers
        working-directory: ${{ github.workspace }}/tests
        run: export APP_URL=$APP_URL &&
          export USERNAME=$USERNAME &&
          export PASSWORD=$PASSWORD &&
          export ORGANISATION_NAME=$ORGANISATION_NAME &&
          npm test && npm test
        working-directory: ${{ github.workspace }}/tests

Solution

  • GitHub actions does process tracking and will automatically kill any executable that was spawned by a step when the step ends.

    There is a way to trick the runner to not kill the process, you need to add some "magic fairy dust" to the step that starts the process:

    RUNNER_TRACKING_ID="" && npm run start:prod &
    

    By clearing the RUNNER_TRACKING_ID temporarily, the runner won't know this process is owned by the workflow.

    Be sure to add a cleanup step with if: always() to kill the process when the job finishes or is cancelled.

    I think you may also be able to set an environment variable

    env: 
      PROCESS_CLEAN: false
    

    To achieve the same result, but I'm not sure.