Skip to content

Commit

Permalink
Playwright setup (#11927)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmrabian authored Nov 21, 2024
1 parent f6972f7 commit 93656be
Show file tree
Hide file tree
Showing 20 changed files with 736 additions and 9 deletions.
8 changes: 8 additions & 0 deletions .env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ CYPRESS_ADMIN_USER_EMAIL=op://mdct_devs/mcr_secrets/CYPRESS_ADMIN_USER_EMAIL
CYPRESS_ADMIN_USER_PASSWORD=op://mdct_devs/mcr_secrets/CYPRESS_ADMIN_USER_PASSWORD # pragma: allowlist secret
CYPRESS_STATE_USER_EMAIL=op://mdct_devs/mcr_secrets/CYPRESS_STATE_USER_EMAIL
CYPRESS_STATE_USER_PASSWORD=op://mdct_devs/mcr_secrets/CYPRESS_STATE_USER_PASSWORD # pragma: allowlist secret

# needed for playwright e2e tests
TEST_ADMIN_USER_EMAIL=op://mdct_devs/mcr_secrets/CYPRESS_ADMIN_USER_EMAIL
TEST_ADMIN_USER_PASSWORD=op://mdct_devs/mcr_secrets/CYPRESS_ADMIN_USER_PASSWORD # pragma: allowlist secret
TEST_STATE_USER_EMAIL=op://mdct_devs/mcr_secrets/CYPRESS_STATE_USER_EMAIL
TEST_STATE_USER_PASSWORD=op://mdct_devs/mcr_secrets/CYPRESS_STATE_USER_PASSWORD # pragma: allowlist secret
TEST_STATE=MN
TEST_STATE=Minnesota
50 changes: 50 additions & 0 deletions .github/workflows/delete-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Delete old folders from GitHub Pages

on:
push:
branches:
- "gh-pages"
schedule:
- cron: '0 0 * * *' # This will run the workflow daily at midnight UTC

jobs:
delete_old_folders:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: gh-pages

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9

- name: Get current directory
run: echo "CURRENT_DIR=$(pwd)" >> $GITHUB_ENV

- name: Run the script
run: python rm_old_folders.py --n-days 30 --folder-name "${{ env.CURRENT_DIR }}"

- name: Commit all changed files back to the repository
uses: stefanzweifel/git-auto-commit-action@v5
with:
branch: gh-pages
commit_message: Delete folders older than 30 days

notify_on_delete_pages_failure:
runs-on: ubuntu-latest
needs:
- delete_old_folders
if: failure()
steps:
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
SLACK_TITLE: ":boom: The nightly delete of expired Playwright reports job has failed in ${{ github.repository }}."
MSG_MINIMAL: true
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}"
99 changes: 98 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ concurrency:

permissions:
id-token: write
contents: read
contents: write
pages: write
actions: read

jobs:
Expand Down Expand Up @@ -279,6 +280,101 @@ jobs:
${{github.workspace}}/tests/cypress/videos/
retention-days: 14

test:
name: Playwright Tests
needs:
- deploy
- register-runner
- e2e-test
- a11y-tests
if: ${{ always() && !cancelled() && needs.deploy.result == 'success' && github.ref_name != 'production' }}
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials for GitHub Actions
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets[env.BRANCH_SPECIFIC_VARNAME_AWS_OIDC_ROLE_TO_ASSUME] || secrets.AWS_OIDC_ROLE_TO_ASSUME }}
aws-region: ${{ secrets[env.BRANCH_SPECIFIC_VARNAME_AWS_DEFAULT_REGION] || secrets.AWS_DEFAULT_REGION }}
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: yarn install
run: yarn install
- name: yarn install tests
run: yarn install
working-directory: tests
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
working-directory: tests
- name: Run Playwright tests
run: yarn playwright test
working-directory: tests
continue-on-error: true
env:
BASE_URL: ${{ needs.deploy.outputs.application_endpoint }}
TEST_STATE_USER_EMAIL: ${{ secrets.CYPRESS_STATE_USER_EMAIL }}
TEST_STATE_USER_PASSWORD: ${{ secrets.CYPRESS_STATE_USER_PASSWORD }}
TEST_ADMIN_USER_EMAIL: ${{ secrets.CYPRESS_ADMIN_USER_EMAIL }}
TEST_ADMIN_USER_PASSWORD: ${{ secrets.CYPRESS_ADMIN_USER_PASSWORD }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-html-report # upload artifact as this name
# path: playwright-report/index.html # path on runner
path: tests/playwright-report # path on runner
retention-days: 30

upload-reports:
name: Upload Reports
needs:
- test
if: ${{ always() && github.ref_name != 'production' }}
runs-on: ubuntu-latest
outputs:
timestamp: ${{ steps.timestampid.outputs.timestamp }}
steps:
# create a unique folder name to put playwright reports in
- name: Set a Timestamp
id: timestampid
run: echo "timestamp=$(date --utc +%Y%m%d_%H%M%SZ)" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: Install dependencies
run: yarn install
# downloads artifact created from the test job
- name: Download reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
name: playwright-html-report # download from previous job
path: downloaded-html-report # save as this when downloaded
- name: Push files to github pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./downloaded-html-report # publish downloaded dir to github pages
destination_dir: ${{ steps.timestampid.outputs.timestamp }}
# need to extract just org name for reassembling the github pages URL
- name: Extract Organization Name
id: extract-org
run: |
echo "ORG_NAME=$(echo $GITHUB_REPOSITORY | cut -d'/' -f1)" >> $GITHUB_ENV
echo "org name: ${ORG_NAME}"
# need to extract just the repo name for reassembling the github pages URL
- name: Extract Repository Name
id: extract-repo
run: |
echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d'/' -f2)" >> $GITHUB_ENV
echo "repo name: ${REPO_NAME}"
# assembles org name, repo name, and unique timestamp to link to github pages url that was published
- name: Write URL in Summary
run: |
echo "## Playwright Test Results" >> $GITHUB_STEP_SUMMARY
echo "https://${ORG_NAME}.github.io/${REPO_NAME}/${{ steps.timestampid.outputs.timestamp }}/" >> $GITHUB_STEP_SUMMARY
cleanup:
name: Delist GHA Runner CIDR Blocks
if: ${{ github.ref_name != 'main' && github.ref_name != 'val' && github.ref_name != 'production' }}
Expand All @@ -287,6 +383,7 @@ jobs:
- register-runner
- a11y-tests
- e2e-test
- test
env:
SLS_DEPRECATION_DISABLE: "*" # Turn off deprecation warnings in the pipeline
steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ tests/cypress/downloads
.vscode/
*._S3rver_cors.xml
services/database/local_buckets
tests/test-results/
tests/playwright-report/
tests/playwright/.cache/
tests/playwright/.auth
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
},
"scripts": {
"test": "cd tests && npm test && cd -",
"test:ci": "cd tests && yarn run test:ci"
"test:ci": "cd tests && yarn run test:ci",
"test:e2e": "cd tests && yarn run test:e2e",
"test:e2e-ui": "cd tests && yarn run test:e2e-ui"
},
"repository": {
"type": "git",
Expand Down
113 changes: 113 additions & 0 deletions rm_old_folders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import argparse
import os
import re
from datetime import datetime, timedelta
import shutil

def find_old_folders(n_days, directory):
"""
Find folders in the specified directory that are older than n_days.
Args:
directory (str): The directory to search for folders.
n_days (int): The number of days to determine which folders to delete.
Returns:
list: List of folder names older than n_days.
"""
current_time = datetime.utcnow()
folder_name_regex = re.compile(r'^\d{8}_\d{6}Z$')

old_folders = []
for entry in os.scandir(directory):
if entry.is_dir() and re.match(folder_name_regex, entry.name):
try:
folder_date = datetime.strptime(entry.name, "%Y%m%d_%H%M%SZ")
time_difference = current_time - folder_date
if time_difference > timedelta(days=n_days):
old_folders.append(entry.name)
else:
print(
f"SKIPPED --- Folder '{entry.name}' is not older than "
f"{n_days} days. It will not be deleted."
)
except ValueError:
print(
f"SKIPPED --- Error parsing timestamp for folder '{entry.name}'. "
f"It will not be deleted."
)
else:
print(
f"SKIPPED --- Found folder/file with name '{entry.name}' that does "
f"not match the expected timestamp format. It will not be deleted."
)

return old_folders

def is_valid_directory(base_directory, folder_path):
"""
Check if the folder_path is a valid directory within the base_directory.
Args:
base_directory (str): The base directory.
folder_path (str): The path of the folder to validate.
Returns:
bool: True if the folder_path is valid, False otherwise.
"""
# Resolve absolute paths
base_directory = os.path.abspath(base_directory)
folder_path = os.path.abspath(folder_path)

# Ensure that the folder_path starts with the base_directory
return folder_path.startswith(base_directory)

def delete_folders(base_directory, folder_names):
"""
Delete specified folders and their contents in the given directory.
Args:
base_directory (str): The base directory containing the folders to delete.
folder_names (list): List of folder names to delete.
"""
for folder_name in folder_names:
folder_path = os.path.join(base_directory, folder_name)
if is_valid_directory(base_directory, folder_path):
try:
shutil.rmtree(folder_path)
print(
f"DELETED --- Folder '{folder_name}' and its contents have "
f"been deleted."
)
except FileNotFoundError:
print(f"Folder '{folder_name}' not found.")
except Exception as e:
print(f"Error deleting folder '{folder_name}': {e}")
else:
print(f"SKIPPED --- Invalid folder path: '{folder_path}'")

if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Delete old folders in a specified directory."
)
parser.add_argument(
"--n-days",
type=int,
required=True,
help="Number of days (days older than current date) to determine "
"which folders to delete."
)
parser.add_argument(
"--folder-name",
type=str,
required=True,
help="Full path to the directory where reports are located."
)
args = parser.parse_args()

# Ensure the provided folder name is an absolute path
if not os.path.isabs(args.folder_name):
raise ValueError("The folder name must be an absolute path.")

old_folders = find_old_folders(args.n_days, args.folder_name)
delete_folders(args.folder_name, old_folders)
6 changes: 5 additions & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
"start": "cd ../ && ./run local && cd -",
"cypress": "cypress open",
"test": "concurrently --kill-others \"yarn start\" \"yarn cypress\"",
"test:ci": "cypress install && cypress run --browser chrome --headless"
"test:ci": "cypress install && cypress run --browser chrome --headless",
"test:e2e": "playwright test",
"test:e2e-ui": "playwright test --ui"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@axe-core/playwright": "^4.10.0",
"@cypress-audit/pa11y": "^1.3.1",
"@playwright/test": "^1.48.0",
"axe-core": "^4.6.3",
"concurrently": "^8.2.2",
"cypress": "^12.17.4",
Expand Down
59 changes: 59 additions & 0 deletions tests/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { defineConfig, devices } from "@playwright/test";
import dotenv from "dotenv";

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
dotenv.config({ path: "../.env" });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "playwright",
testMatch: ["**/*.spec.js", "**/*.spec.ts"],
/* Run tests in files in parallel */
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.BASE_URL || "http://localhost:3000",

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",

/* Video recording configuration */
video: "retain-on-failure",
},

/* Configure projects for major browsers */
projects: [
{
name: "setup",
use: { ...devices["Desktop Chrome"] },
testMatch: /.*\.setup\.ts/,
},
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
dependencies: ["setup"],
},
],

/* Run your local dev server before starting the tests */
webServer: {
command: process.env.CI ? "" : "cd ../ && ./run local",
url: process.env.BASE_URL || "http://localhost:3000",
reuseExistingServer: !!process.env.CI,
stdout: "pipe",
},
});
Loading

0 comments on commit 93656be

Please sign in to comment.