Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playwright setup #11927

Merged
merged 9 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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