Pressing button in Laravel Dusk test results in blank screen on GitHub Actions

941 Views Asked by At

I've written the following end-to-end test with Laravel Dusk to test my site's PayPal and Stripe Element forms:

<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class PaymentsTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
    * A Dusk test example.
    * 
    * @return void
    */
    public function test_payments_are_working()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit("/manufacturers/oneplus")
            ->type('email', "[email protected]")
            ->press('Continue')
            ->waitForLocation('/payment')
            ->waitFor("#paypal-button-container iframe")
            ->withinFrame('#paypal-button-container iframe', function($browser) {
                $browser->click(".paypal-button");
            })
            ->waitFor(".paypal-checkout-sandbox-iframe")
            ->withinFrame('.paypal-checkout-sandbox-iframe', function($browser) {
                $browser->click(".paypal-checkout-close");
            })
            ->waitFor('.__PrivateStripeElement iframe')
            ->withinFrame('.__PrivateStripeElement iframe', function($browser) {
                $browser
                ->pause(1000)
                ->keys('input[placeholder="1234 1234 1234 1234"]', 4242424242424242)
                ->keys('input[placeholder="MM / YY"]', '0125')
                ->keys('input[placeholder="CVC"]', '123')
                ->select('select[name="country"]', 'GB')
                ->keys('input[name="postalCode"]', 'SW1A 1AA')
                ->pause(500);
            })
            ->clickAndWaitForReload("@stripe-payment-button")
            ->pause(10000) // for debugging purposes
            ->assertSeeAnythingIn(".text-success");
        });
    }
}

The entire test works right up until the final assertion:

Time: 00:19.175, Memory: 30.00 MB

There was 1 error:

1) Tests\Browser\PaymentsTest::test_payments_are_working
Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"body .text-success"}
(Session info: headless chrome=100.0.4896.75)

Looking at the screenshot generated by my GitHub Action workflow at the point of failure, I can see that immediately after the button is pressed it becomes a blank page instead of redirecting to the expected route, even though prior to this all the routes are working.

What could be going on here that would cause this one route to become blank?

What I've Tried So Far:

  • Setting APP_URL in .env.dusk.testing to http://127.0.0.1:8000 and running Dusk with php artisan dusk --env=testing
  • Adding a step to run migrations in my GitHub Action, which was missing from the example in the documentation
1

There are 1 best solutions below

0
On BEST ANSWER

After a few days of trying to debug this infuriating issue in order to test my Stripe and PayPal payments - made only a little easier by watching the tests run locally using php artisan dusk --browse - I eventually realised the primary cause of the issue was a HTTPS issue despite having already set my APP_URL to HTTP... although even after solving that issue I still had to fix a few other issues with the Dusk documentation's example GitHub Action just to get the whole setup working.


I first combed through my Stripe client side code and confirmed that my return_url variable was hardcoded to a HTTPS link on successful payment, which is why that route alone was failing to display. Simply changing this variable to http (neither the Stripe nor PayPal payment success routes accept relative links) would solve the problem, but pushing this file into version control will also cause the live site to redirect the insecure HTTP route, which itself will cause the Stripe live integration to fail. Therefore this requires a solution which avoids hardcoding the payment return URLs into the JavaScript.

I decided to solve it by using Laravel Mix's ability to dynamically inject variables from my .env file into my JavaScript, as briefly described in the documentation here.

First, make sure your .env.dusk.testing file contains the following values:

APP_ENV=testing
APP_URL=http://127.0.0.1:8000 # Leave this as is
MIX_APP_URL="${APP_URL}"

Second, make sure your .env.dusk.local file contains the following values:

APP_ENV=local
APP_URL=http://localhost # This should be a HTTP link to your local development server
MIX_APP_URL="${APP_URL}"

Finally, make sure the .env file on your production server has something like the following:

APP_ENV=production
APP_URL=https://www.example.com # Make sure this is HTTPS
MIX_APP_URL="${APP_URL}"

In my client-side JavaScript code, such as for Stripe payments, I can then grab the environment's relevant APP_URL by doing:

const { error } = await stripe.confirmPayment({
    elements,
    confirmParams: {
        return_url: process.env.MIX_APP_URL + "/success",
    },
});

...where /success represents the rest of the path to my payment success route.

Next, add the standard setup-node action to install a recent version of NPM to your GitHub Action, followed by a build step to run Laravel Mix and replace process.env.MIX_APP_URL your other Mix variables with the appropriate values:

  - uses: actions/setup-node@v3
    with:
      node-version: "14"

 ...

  - name: Build NPM for production
    run: |
      npm ci
      npm run dev

Lastly, change the environment file preparation stage to copy environment variables from env.dusk.testing (or .env.dusk.local if that's all you have) instead of the default .env.example used by the documentation:

  - name: Prepare The Environment
    run: cp .env.dusk.testing .env

This should result in a complete testing workflow for GitHub Actions that looks similar to the following:

jobs:
  dusk-php:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "14"
      - name: Prepare The Environment
        run: cp .env.dusk.testing .env
      - name: Create Database
        run: |
          sudo systemctl start mysql
          mysql --user="root" --password="root" -e "CREATE DATABASE CICD_test_db character set UTF8mb4 collate utf8mb4_bin;"
      - name: Install Composer Dependencies
        run: composer install --no-progress --prefer-dist --optimize-autoloader
      - name: Build NPM for production
        run: |
          npm ci
          npm run prod
      - name: Generate Application Key
        run: php artisan key:generate
      - name: Upgrade Chrome Driver
        run: php artisan dusk:chrome-driver `/opt/google/chrome/chrome --version | cut -d " " -f3 | cut -d "." -f1`
      - name: Run Migrations
        run: php artisan migrate:fresh
      - name: Start Chrome Driver
        run: ./vendor/laravel/dusk/bin/chromedriver-linux &
      - name: Run Laravel Server
        run: php artisan serve --no-reload &
      - name: Run Dusk Tests
        run: php artisan dusk -vvv --env=testing
      - name: Upload Screenshots
        if: failure()
        uses: actions/upload-artifact@v2
        with:
          name: screenshots
          path: tests/Browser/screenshots
      - name: Upload Console Logs
        if: failure()
        uses: actions/upload-artifact@v2
        with:
          name: console
          path: tests/Browser/console

...and most importantly, successfully runs your Laravel Dusk tests.