diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1edbfb8 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,30 @@ +{ + "name": "Django Docker Environment", + "remoteUser": "vscode", + "postCreateCommand": "git config --global --add safe.directory /home/web/project", + "dockerComposeFile": [ + "../deployment/docker-compose.yml", + "../deployment/docker-compose.override.devcontainer.yml" + ], + "service": "dev", + "workspaceFolder": "/home/web/project", + "settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.pylintEnabled": true, + "python.linting.enabled": true + }, + "runArgs": [ + "--env-file", + "../deployment/.env" + ], + "portsAttributes": { + "9000": { + "label": "Frontend", + "onAutoForward": "notify" + } + }, + "forwardPorts": [8000, 9000], + "extensions": ["ms-python.python", "ms-azuretools.vscode-docker"], + "shutdownAction": "stopCompose" + } \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5b56c5d --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +deployment/volumes \ No newline at end of file diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..1644a0a --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +use nix +layout python3 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..53f7d8c --- /dev/null +++ b/.flake8 @@ -0,0 +1,10 @@ +[flake8] +exclude = */docs/*,*/.tox/*,*/.venv/*,.pycharm_helpers/*,*/migrations/*,docs/*,*/__init__.py,scripts/*,deployment/*,django_project/initialize.py +max-line-length = 79 + +# E12x continuation line indentation +# E251 no spaces around keyword / parameter equals +# E303 too many blank lines (3) +# F405 name may be undefined, or defined from star imports: module +ignore = E125,E126,E251,E303,F405,W504,W605,F901,D105,W503 +extend-exclude=django_project/frontend/node_modules/,django_project/core/settings/secret.py \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..0c39914 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,16 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false \ No newline at end of file diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..f0f6bfd --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,98 @@ +# Context Layer Management. +name: Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + flake8_py3: + name: Python Lint + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.7 + architecture: x64 + - name: Checkout PyTorch + uses: actions/checkout@master + - name: Install flake8 + run: pip install flake8 + - name: Install flake8-docstrings + run: pip install flake8-docstrings + - name: Run flake8 + uses: suo/flake8-github-action@releases/v1 + with: + checkName: 'Python Lint' # NOTE: this needs to be the same as the job name + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + django_app_test: + name: 'Django App' + runs-on: ubuntu-latest + env: + APP_IMAGE: kartoza/context_layer_management + steps: + - uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Build test image + uses: docker/build-push-action@v2 + with: + context: . + file: deployment/docker/Dockerfile + push: false + load: true + target: dev + tags: ${{ env.APP_IMAGE }}:dev + cache-from: | + type=gha,scope=test + type=gha,scope=prod + cache-to: type=gha,scope=test + + - name: Run docker-compose services + working-directory: deployment + run: | + echo "Override docker-compose for testing purposes" + cp docker-compose.test.yml docker-compose.override.yml + cp .template.env .env + cd ../ + make dev + make wait-db + make dev-entrypoint + make dev-runserver + make dev-load-demo-data + make sleep + + - name: Test production config ready + run: make production-check + + - name: Test django endpoint + run: | + curl http://localhost:5000/ + if [ $? -ne 0 ]; then + echo "Curl command failed" + exit 1 + fi + + - name: Test E2E + working-directory: ./playwright/ci-test + run: | + npm install + npm ci + npx playwright install --with-deps + npx playwright test --workers 1 + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright/ci-test/playwright-report/ + retention-days: 30 + + - name: Test backend + run: make dev-test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd7ca1f --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +.idea +.venv +.iml +deployment/.env +deployment/docker-compose.override.yml +deployment/volumes + +__pycache__/ + +django_project/.pycharm_helpers/ +celerybeat-schedule* +node_modules/ +webpack-stats.* +secret.py +coverage.xml + +django_project/.env +django_project/tmp/ +django_project/frontend/bundles/ +django_project/frontend/stats.json +django_project/frontend/package-lock.json +django_project/frontend/webpack-stats.dev.json +django_project/frontend/webpack-stats.prod.json + +docs/site/* +docks/mkdocs.yml +.direnv +docs/mkdocs.yml diff --git a/.run/DJANGO+REACT.run.xml b/.run/DJANGO+REACT.run.xml new file mode 100644 index 0000000..7a344a9 --- /dev/null +++ b/.run/DJANGO+REACT.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Run Server.run.xml b/.run/Run Server.run.xml new file mode 100644 index 0000000..1ba1383 --- /dev/null +++ b/.run/Run Server.run.xml @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/.run/serve.run.xml b/.run/serve.run.xml new file mode 100644 index 0000000..3cd689c --- /dev/null +++ b/.run/serve.run.xml @@ -0,0 +1,12 @@ + + + + + + + +{% block content %} +{% endblock %} + + diff --git a/django_project/frontend/templates/home.html b/django_project/frontend/templates/home.html new file mode 100644 index 0000000..7e6f2b0 --- /dev/null +++ b/django_project/frontend/templates/home.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block extra_head %} + {% load render_bundle from webpack_loader %} +{% endblock %} + +{% block content %} + + +
+{% render_bundle 'App' 'css' %} +{% render_bundle 'App' 'js' %} + +{% endblock %} + + diff --git a/django_project/frontend/tsconfig.json b/django_project/frontend/tsconfig.json new file mode 100644 index 0000000..4f6d8d4 --- /dev/null +++ b/django_project/frontend/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "outDir": "./bundles/", + "noImplicitAny": true, + "module": "esnext", + "target": "es5", + "jsx": "react", + "allowJs": true, + "sourceMap": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": [ + "ES2021.String", + "dom" + ], + }, +} diff --git a/django_project/frontend/urls.py b/django_project/frontend/urls.py new file mode 100644 index 0000000..c483c5b --- /dev/null +++ b/django_project/frontend/urls.py @@ -0,0 +1,10 @@ +"""Context Layer Management.""" + +from django.urls import path + +from .views import HomeView, SentryProxyView + +urlpatterns = [ + path('', HomeView.as_view(), name='home'), + path('sentry-proxy/', SentryProxyView.as_view(), name='sentry-proxy'), +] diff --git a/django_project/frontend/views.py b/django_project/frontend/views.py new file mode 100644 index 0000000..9c72537 --- /dev/null +++ b/django_project/frontend/views.py @@ -0,0 +1,51 @@ +"""Context Layer Management.""" + +import json +from urllib.parse import urlparse + +import requests +from django.conf import settings +from django.http import HttpResponse +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import TemplateView, View + + +class HomeView(TemplateView): + """Home page view.""" + + template_name = 'home.html' + + +@method_decorator(csrf_exempt, name="dispatch") +class SentryProxyView(View): + """View for handling sentry.""" + + sentry_key = settings.SENTRY_DSN + + def post(self, request): + """Post sentry data.""" + host = "sentry.io" + + envelope = request.body.decode("utf-8") + pieces = envelope.split("\n", 1) + header = json.loads(pieces[0]) + + if "dsn" in header: + dsn = urlparse(header["dsn"]) + project_id = int(dsn.path.strip("/")) + + sentry_url = f"https://{host}/api/{project_id}/envelope/" + headers = { + "Content-Type": "application/x-sentry-envelope", + } + response = requests.post( + sentry_url, + headers=headers, + data=envelope.encode("utf-8"), + timeout=200 + ) + + return HttpResponse(response.content, status=response.status_code) + + return HttpResponse(status=400) diff --git a/django_project/frontend/webpack.config.js b/django_project/frontend/webpack.config.js new file mode 100644 index 0000000..53735c4 --- /dev/null +++ b/django_project/frontend/webpack.config.js @@ -0,0 +1,128 @@ +const path = require("path"); +const BundleTracker = require('webpack-bundle-tracker'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // require clean-webpack-plugin +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +const webpack = require("webpack"); + +const mode = process.env.npm_lifecycle_script; +const isDev = (mode.includes('dev')); +const isServe = (mode.includes('serve')); +const filename = isDev ? "[name]" : "[name].[fullhash]"; +const statsFilename = isDev ? './webpack-stats.dev.json' : './webpack-stats.prod.json'; +const minimized = !isDev; + +let conf = { + entry: { + App: './src/App.tsx' + }, + output: { + path: path.resolve(__dirname, "./bundles/frontend"), + filename: filename + '.js' + }, + module: { + rules: [ + { + test: /\.tsx?$/, + exclude: /node_modules/, + use: [{ loader: 'ts-loader' }], + }, + { + test: /\.s[ac]ss$/i, + use: [ + MiniCssExtractPlugin.loader, + "css-loader", + "sass-loader" + ], + }, + { + test: /\.css$/i, + use: [ + // Translates CSS into CommonJS + MiniCssExtractPlugin.loader, "css-loader", + ], + }, + ], + }, + optimization: { + minimize: minimized, + splitChunks: { + cacheGroups: { + styles: { + name: "styles", + type: "css/mini-extract", + chunks: "all", + enforce: true, + }, + }, + }, + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), + }), + new CleanWebpackPlugin(), + new BundleTracker({ filename: statsFilename }), + new MiniCssExtractPlugin({ + filename: filename + '.css', + chunkFilename: filename + '.css', + ignoreOrder: true, + }), + ], + resolve: { + modules: ['node_modules'], + extensions: [".ts", ".tsx", ".js", ".css", ".scss"], + fallback: { + fs: false, + } + }, + watchOptions: { + ignored: ['node_modules', './**/*.py'], + aggregateTimeout: 300, + poll: 1000 + } +}; +if (isServe) { + if (isDev) { + conf['output'] = { + path: path.resolve(__dirname, "./bundles/frontend"), + filename: filename + '.js', + publicPath: 'http://localhost:9000/static/', + } + } + conf['devServer'] = { + hot: true, + port: 9000, + headers: { + 'Access-Control-Allow-Origin': '*' + }, + devMiddleware: { + writeToDisk: true, + }, + allowedHosts: 'all', + compress: true, + } + conf['devtool'] = 'inline-source-map', + conf['output'] = { + path: path.resolve(__dirname, "./bundles/frontend"), + filename: filename + '.js', + publicPath: 'http://localhost:9000/static/', + } + conf['plugins'].push( + new ReactRefreshWebpackPlugin() + ) +} else if (isDev) { + conf['output'] = { + path: path.resolve(__dirname, "./bundles/frontend"), + filename: filename + '.js' + } + conf['devServer'] = { + hot: true, + port: 9000, + writeToDisk: true, + headers: { + "Access-Control-Allow-Origin": "*", + } + } +} +module.exports = conf; diff --git a/django_project/initialize.py b/django_project/initialize.py new file mode 100644 index 0000000..60e08be --- /dev/null +++ b/django_project/initialize.py @@ -0,0 +1,92 @@ +"""Context Layer Management.""" + +######################################################### +# Setting up the context +######################################################### + +import ast +import os +import time + +import django +from django.contrib.auth import get_user_model +from django.core.management import call_command +from django.db import connection +from django.db.utils import OperationalError + +django.setup() + +# Getting the secrets +admin_username = os.getenv('ADMIN_USERNAME') +admin_password = os.getenv('ADMIN_PASSWORD') +admin_email = os.getenv('ADMIN_EMAIL') + +######################################################### +# 1. Waiting for PostgreSQL +######################################################### + +print("-----------------------------------------------------") +print("1. Waiting for PostgreSQL") +for _ in range(60): + try: + connection.ensure_connection() + break + except OperationalError: + time.sleep(1) +else: + connection.ensure_connection() +connection.close() + +######################################################### +# 2. Running the migrations +######################################################### + +print("-----------------------------------------------------") +print("2. Running the migrations") +call_command('makemigrations') +call_command('migrate', '--noinput') + +######################################################### +# 3. Creating superuser if it doesn't exist +######################################################### + +print("-----------------------------------------------------") +print("3. Creating/updating superuser") +try: + superuser = get_user_model().objects.get(username=admin_username) + superuser.set_password(admin_password) + superuser.is_active = True + superuser.email = admin_email + superuser.save() + print('superuser successfully updated') +except get_user_model().DoesNotExist: + superuser = get_user_model().objects.create_superuser( + admin_username, + admin_email, + admin_password + ) + print('superuser successfully created') + +######################################################### +# 4. Loading fixtures +######################################################### + +print("-----------------------------------------------------") +print("4. Loading fixtures") + +# Disable fixtures loading in prod by including environment variable: +# INITIAL_FIXTURES=False + +_load_initial_fixtures = ast.literal_eval( + os.getenv('INITIAL_FIXTURES', 'False') +) +if _load_initial_fixtures: + call_command('load_fixtures') + +######################################################### +# 5. Collecting static files +######################################################### + +print("-----------------------------------------------------") +print("5. Collecting static files") +call_command('collectstatic', '--noinput', verbosity=0) diff --git a/django_project/manage.py b/django_project/manage.py new file mode 100755 index 0000000..f2a662c --- /dev/null +++ b/django_project/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/django_project/webpack_serve.sh b/django_project/webpack_serve.sh new file mode 100755 index 0000000..c4d446b --- /dev/null +++ b/django_project/webpack_serve.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "TEST" +whoami +npm --prefix /home/web/django_project/frontend run serve diff --git a/playwright/ci-test/.envrc b/playwright/ci-test/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/playwright/ci-test/.envrc @@ -0,0 +1 @@ +use nix diff --git a/playwright/ci-test/.gitignore b/playwright/ci-test/.gitignore new file mode 100644 index 0000000..e0258ff --- /dev/null +++ b/playwright/ci-test/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +states +package-lock.json +playwright-results +*auth.json \ No newline at end of file diff --git a/playwright/ci-test/README.md b/playwright/ci-test/README.md new file mode 100644 index 0000000..42a38f1 --- /dev/null +++ b/playwright/ci-test/README.md @@ -0,0 +1,98 @@ +# Validation Tests + +These tests are designed to run from GitHub action or CI. + +They are intended to verify basic functionality is working during the building of the application(Just before deployment to staging or production). + +## Essential reading: + +* [https://playwright.dev/](https://playwright.dev/) +* [https://playwright.dev/docs/ci-intro](https://playwright.dev/docs/ci-intro) +* [https://direnv.net/docs/installation.html](https://direnv.net/docs/installation.html) + +## Setting up your environment + +Before you can run, you need to set up your environment. + +Running these tests requires playwright set up on your local machine, as well as NodeJS. + +### NixOS + +If you are a NixOS user, you can set up direnv and then cd into this directory in your shell. + +When you do so the first time, you will be prompted to allow direnv which you can do using this command: + +```bash +direnv allow +``` + +>  This may take a while the first time as NixOS builds you a sandbox environment. + +### Non-NixOS + +For a non-NixOS user(Debian/Ubuntu) set up your environment by the following commands: + +```bash +npm install +``` + +To install playwright browsers with OS-level dependencies use: + +```bash +npx playwright install --with-deps chromium +``` + +**NOTE:** This only works with Debian/Ubuntu as they receive official support from playwright. It will also request your master password to install the dependencies. + +## Recording a test + +There is a bash helper script that will let you quickly create a new test: + +``` +Usage: ./record-test.sh TESTNAME +e.g. ./record-test.sh mytest +will write a new test to tests/mytest.spec.ts +Do not use spaces in your test name. +Test files MUST END in .spec.ts + +After recording your test, close the test browser. +You can then run your test by doing: +./run-tests.sh +``` + +>  The first time you record a test, it will store your session credentials in a file ending in ``auth.json``. This file should **NEVER** be committed to git / shared publicly. There is a gitignore rule to ensure this. + +## Running a test + +By default, this will run in `headless` mode just as it is in CI. + +```bash +./run-tests.sh +``` + +**NOTE:** To run it in `UI` mode, add the `--ui` tag to the script. + +```bash +$PLAYWRIGHT \ + test \ + --ui \ + --project chromium +``` + +## Adding a CI test + +To add tests for CI, use the recorded tests then modify it for CI. + +The tests can be modified to include time-outs, and waiting for events/actions etc. For more look go through [playwright's documentation](https://playwright.dev/docs/writing-tests). + +An example of a line-recorded test would look like: + +```typescript +await page.getByRole('img', { name: 'image' }).click(); +``` + +For the CI the line could be modified and turned into an assertion using `expect` to test if the specific element is visible. + +```typescript +await expect(page.getByRole('img', { name: 'image' })).toBeVisible(); +``` diff --git a/playwright/ci-test/base-url.sh b/playwright/ci-test/base-url.sh new file mode 100755 index 0000000..808ac7d --- /dev/null +++ b/playwright/ci-test/base-url.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Setting BASE_URL for test site" +BASE_URL=http://localhost:61100 diff --git a/playwright/ci-test/create-auth.sh b/playwright/ci-test/create-auth.sh new file mode 100755 index 0000000..386a047 --- /dev/null +++ b/playwright/ci-test/create-auth.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +source base-url.sh + +source playwright-path.sh + +echo "This script will write a new test to tests/deleteme.spec.ts" +echo "then delete it, leaving only the auth config." +echo "" +echo "When the playwright browser opens, log in to the site then exit." +echo "After recording your test, close the test browser." +echo "Recording auth token to auth.json" + +# File exists and write permission granted to user +# show prompt +echo "Continue? y/n" +read ANSWER +case $ANSWER in + [yY] ) echo "Writing auth.json" ;; + [nN] ) echo "Cancelled."; exit ;; +esac + +$PLAYWRIGHT \ + codegen \ + --target playwright-test \ + --save-storage=auth.json \ + -o tests/deleteme.spec.ts \ + $BASE_URL + +# We are only interested in auth.json +rm tests/deleteme.spec.ts + +echo "Auth file creation completed." +echo "You can then run your tests by doing e.g.:" +echo "playwright test --project chromium" diff --git a/playwright/ci-test/nodesource-install.sh b/playwright/ci-test/nodesource-install.sh new file mode 100755 index 0000000..eb2ef0f --- /dev/null +++ b/playwright/ci-test/nodesource-install.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +echo "NodeJS will be installed if not present" +echo "sudo password will be required" + +USES_APT=$(which apt | grep -w "apt" | wc -l) +USES_RPM=$(which rpm | grep -w "rpm" | wc -l) + +if [ $USES_APT -eq 1 ]; then + curl -SLO https://deb.nodesource.com/nsolid_setup_deb.sh + sudo chmod 500 nsolid_setup_deb.sh + sudo ./nsolid_setup_deb.sh 20 + sudo apt-get install nodejs -y + +elif [ $USES_RPM -eq 1 ]; then + curl -SLO https://rpm.nodesource.com/nsolid_setup_rpm.sh + sudo chmod 500 nsolid_setup_rpm.sh + sudo ./nsolid_setup_rpm.sh 20 + sudo yum install nodejs -y --setopt=nodesource-nodejs.module_hotfixes=1 +fi + +echo "Done" +echo "" diff --git a/playwright/ci-test/package.json b/playwright/ci-test/package.json new file mode 100644 index 0000000..b0c259c --- /dev/null +++ b/playwright/ci-test/package.json @@ -0,0 +1,20 @@ +{ + "name": "ci-test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.39.0", + "@types/node": "^20.8.9" + }, + "dependencies": { + "playwright": "^1.39.0" + } +} diff --git a/playwright/ci-test/playwright-path.sh b/playwright/ci-test/playwright-path.sh new file mode 100755 index 0000000..41a24a0 --- /dev/null +++ b/playwright/ci-test/playwright-path.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +echo "This script will discover the path to your playwright install" +echo "If you are not in a NixOS environment and it is not installed," +echo "playwright will be installed." +echo "" +echo "At the end of calling this script , you should have a PLAYWRIGHT" +echo "" + +# Are we on nixos or a distro with Nix installed for packages +# Y + # Are we in direnv? + # Y: should all be set up + # N: run nix-shell +#N + # Are we in a deb based distro? + # Are we in an rpm based distro? + # Are we on macOS? + # Are we in windows? + +HAS_PLAYWRIGHT=$(which playwright 2> /dev/null | grep -v "which: no" | wc -l) +PLAYWRIGHT="playwright" +if [ $HAS_PLAYWRIGHT -eq 0 ]; then + PLAYWRIGHT="npx playwright" + + # check if OS is a deb based distro and uses apt + USES_APT=$(which apt 2> /dev/null | grep -w "apt" | wc -l) + # check if OS is an rpm-based distro + USES_RPM=$(which rpm | grep -w "rpm" | wc -l) + + if [ $USES_APT -eq 1 ]; then + # check if nodejs is installed + HAS_NODEJS=$(which node | grep -w "node" | wc -l) + + # if nodejs is present then + if [ $HAS_NODEJS -eq 0 ]; then + source nodesource-install.sh + fi + + # check if npm is present + HAS_NPM=$(which npm | grep -w "npm" | wc -l) + + if [ $HAS_NPM -eq 1 ]; then + NPM="npm" + PLAYWRIGHT_INSTALL=$($NPM ls --depth 1 playwright | grep -w "@playwright/test" | wc -l) + + if [ $PLAYWRIGHT_INSTALL -eq 0 ]; then + $NPM install -D @playwright/test@latest + $NPM ci + $PLAYWRIGHT install --with-deps chromium + fi + + fi + + elif [ $USES_RPM -eq 1 ]; then + + # check if nodejs is installed + HAS_NODEJS=$(which node | grep -w "node" | wc -l) + + # if nodejs is present then + if [ $HAS_NODEJS -eq 0 ]; then + source nodesource-install.sh + fi + + # check if npm is present + HAS_NPM=$(which npm | grep -w "npm" | wc -l) + + if [ $HAS_NPM -eq 1 ]; then + NPM="npm" + PLAYWRIGHT_INSTALL=$($NPM ls --depth 1 playwright | grep -w "@playwright/test" | wc -l) + + if [ $PLAYWRIGHT_INSTALL -eq 0 ]; then + $NPM install -D @playwright/test@latest + $NPM ci + $PLAYWRIGHT install + fi + + fi + fi +fi + +echo "Done." +echo "" diff --git a/playwright/ci-test/playwright.config.ts b/playwright/ci-test/playwright.config.ts new file mode 100644 index 0000000..f1f554f --- /dev/null +++ b/playwright/ci-test/playwright.config.ts @@ -0,0 +1,86 @@ + +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ + +//require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + timeout: 30 * 1000, + fullyParallel: true, + /* 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: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + baseURL: process.env.STAGING === '1' ? 'http://example.com' : 'http://localhost:61100/' + }, + + /* Configure projects for major browsers */ + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], + // Use prepared auth state. + storageState: 'auth.json', + }, + dependencies: ['setup'], + }, + // + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/playwright/ci-test/record-test.sh b/playwright/ci-test/record-test.sh new file mode 100755 index 0000000..6b4fb74 --- /dev/null +++ b/playwright/ci-test/record-test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +source base-url.sh + +source playwright-path.sh + +if [ -z "$1" ] +then + echo "Usage: $0 TESTNAME" + echo "e.g. $0 mytest" + echo "will write a new test to tests/mytest.spec.ts" + echo "Do not use spaces in your test name." + echo "" + echo "After recording your test, close the test browser." + echo "You can then run your test by doing:" + echo "npx playwright test tests/mytest.spec.py" + exit +else + echo "Recording test to tests/${1}" +fi + +if [ -w "tests/${1}.spec.ts" ]; then + # File exists and write permission granted to user + # show prompt + echo "File tests/${1}.spec.ts exists. Overwrite? y/n" + read ANSWER + case $ANSWER in + [yY] ) echo "Writing recorded test to tests/${1}.spec.ts" ;; + [nN] ) echo "Cancelled."; exit ;; + esac +fi +TESTNAME=$1 + +$PLAYWRIGHT \ + codegen \ + --target playwright-test \ + --load-storage=auth.json \ + -o tests/$1.spec.ts \ + $BASE_URL + + +echo "Test recording completed." +echo "You can then run your test by doing:" +echo "./run-tests.sh" diff --git a/playwright/ci-test/run-tests.sh b/playwright/ci-test/run-tests.sh new file mode 100755 index 0000000..5ff10ab --- /dev/null +++ b/playwright/ci-test/run-tests.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +source playwright-path.sh + +echo "This script will run the tests defined in tests/" +echo "Before running the tests you need to create the auth config" +echo "" + +$PLAYWRIGHT \ + test \ + --ui \ + --project chromium + +echo "--done--" diff --git a/playwright/ci-test/shell.nix b/playwright/ci-test/shell.nix new file mode 100644 index 0000000..595c145 --- /dev/null +++ b/playwright/ci-test/shell.nix @@ -0,0 +1,28 @@ +let + # + # Note that I am using a snapshot from NixOS unstable here + # so that we can use a more bleeding edge version which includes the test --ui . + # If you want use a different version, go to nix packages search, and find the + # github hash of the version you want to be using, then replace in the URL below. + # + nixpkgs = builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/4059c4f71b3a7339261c0183e365fd8016f24bdb.tar.gz"; + pkgs = import nixpkgs { config = { }; overlays = [ ]; }; +in +with pkgs; +mkShell { + buildInputs = [ + nodejs + playwright-test + # python311Packages.playwright + # python311Packages.pytest + ]; + + PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}"; + + shellHook = '' + # Remove playwright from node_modules, so it will be taken from playwright-test + rm node_modules/@playwright/ -R + export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} + export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true + ''; +} diff --git a/playwright/ci-test/tests/auth.setup.ts b/playwright/ci-test/tests/auth.setup.ts new file mode 100644 index 0000000..0f5ead4 --- /dev/null +++ b/playwright/ci-test/tests/auth.setup.ts @@ -0,0 +1,38 @@ +import { test as setup, expect } from '@playwright/test'; + +let url = '/'; + +let user_email = 'admin@example.com'; +let password = 'admin'; +const authFile = 'auth.json'; + + +setup.describe('login', () => { + + setup('login', async ({page}) => { + + await page.goto(url); + + const buttonSelector = 'LOGIN'; + + await page.waitForSelector(buttonSelector, {timeout: 2000}); + + const initialURL = page.url(); + + await page.click(buttonSelector); + + await page.waitForURL('**/login/'); + + await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible(); + + await page.getByPlaceholder('E-mail address').fill(user_email); + + await page.getByPlaceholder('Password').fill(password); + + await page.getByRole('button', { name: 'LOGIN' }).click(); + + await page.context().storageState({ path: authFile }); + + }); + +}); diff --git a/playwright/ci-test/tests/landingPage.spec.ts b/playwright/ci-test/tests/landingPage.spec.ts new file mode 100644 index 0000000..83b3eb0 --- /dev/null +++ b/playwright/ci-test/tests/landingPage.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +let url = '/'; + + +test.describe('navigation', () => { + test.beforeEach(async ({ page }) => { + // Go to the starting url before each test. + await page.goto(url); + }); + + test('has title', async ({ page }) => { + await page.waitForSelector('.landing-page-banner-text-header', { timeout: 2000 }); + await expect(page.locator('div.landing-page-banner-text-header')).toHaveText(); + }) + test('navigates', async ({page}) => { + }); +}); diff --git a/playwright/staging-tests/.envrc b/playwright/staging-tests/.envrc new file mode 100644 index 0000000..1d953f4 --- /dev/null +++ b/playwright/staging-tests/.envrc @@ -0,0 +1 @@ +use nix diff --git a/playwright/staging-tests/.gitignore b/playwright/staging-tests/.gitignore new file mode 100644 index 0000000..af81d4c --- /dev/null +++ b/playwright/staging-tests/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +*auth.json +.env +.direnv/ +/test-results/ +__pycache__ diff --git a/playwright/staging-tests/README.md b/playwright/staging-tests/README.md new file mode 100644 index 0000000..ac0de06 --- /dev/null +++ b/playwright/staging-tests/README.md @@ -0,0 +1,72 @@ +# Validation Tests + +These tests are designed to run from your local machine (i.e. not from a GitHub action or CI). +The are intended to verify basic functionality is working after a deployment is made to staging, and prior to deployment to production. + +## Essential reading: + +* https://playwright.dev/ +* https://direnv.net/docs/installation.html + +## Setting up your environment + +Before you can run or record tests, you need to set up your environment. + +Running these tests require playwright set up on your local machine, as well as NodeJS. + +**NixOS** + +If you are a NixOS user, you can set up direnv and then cd into this directory in your shell. + +When you do so the first time, you will be prompted to allow direnv which you can do using this command: + + +``` +direnv allow +``` + +>  This may take a while the first time as NixOS builds you a sandbox environment. + +**NON-NixOS** + +For a non-NixOS user(Debian/Ubuntu) set up your environment by the following commands: + +```bash +npm install +``` + +To install playwright browsers with OS-level dependencies use: + + +```bash +npm playwright install --with-deps chromium +``` + +**NOTE:** This only works with Debian/Ubuntu as they recieve official support from playwright. It will also request your master password to install the dependencies. + +## Recording a test + +There is a bash helper script that will let you quickly create a new test: + +``` +Usage: ./record-test.sh TESTNAME +e.g. ./record-test.sh mytest +will write a new test to tests/mytest.spec.ts +Do not use spaces in your test name. +Test files MUST END in .spec.ts + +After recording your test, close the test browser. +You can then run your test by doing: +./run-tests.sh +``` + + +>  The first time you record a test, it will store your session credentials in a file ending in ``auth.json``. This file should **NEVER** be committed to git / shared publicly. There is a gitignore rule to ensure this. + +## Running a test + + +``` +./run-tests.sh +``` + diff --git a/playwright/staging-tests/base-url.sh b/playwright/staging-tests/base-url.sh new file mode 100755 index 0000000..2b3082e --- /dev/null +++ b/playwright/staging-tests/base-url.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Setting BASE_URL for test site" +BASE_URL=https://example.com diff --git a/playwright/staging-tests/create-auth.sh b/playwright/staging-tests/create-auth.sh new file mode 100755 index 0000000..386a047 --- /dev/null +++ b/playwright/staging-tests/create-auth.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +source base-url.sh + +source playwright-path.sh + +echo "This script will write a new test to tests/deleteme.spec.ts" +echo "then delete it, leaving only the auth config." +echo "" +echo "When the playwright browser opens, log in to the site then exit." +echo "After recording your test, close the test browser." +echo "Recording auth token to auth.json" + +# File exists and write permission granted to user +# show prompt +echo "Continue? y/n" +read ANSWER +case $ANSWER in + [yY] ) echo "Writing auth.json" ;; + [nN] ) echo "Cancelled."; exit ;; +esac + +$PLAYWRIGHT \ + codegen \ + --target playwright-test \ + --save-storage=auth.json \ + -o tests/deleteme.spec.ts \ + $BASE_URL + +# We are only interested in auth.json +rm tests/deleteme.spec.ts + +echo "Auth file creation completed." +echo "You can then run your tests by doing e.g.:" +echo "playwright test --project chromium" diff --git a/playwright/staging-tests/nodesource-install.sh b/playwright/staging-tests/nodesource-install.sh new file mode 100755 index 0000000..eb2ef0f --- /dev/null +++ b/playwright/staging-tests/nodesource-install.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +echo "NodeJS will be installed if not present" +echo "sudo password will be required" + +USES_APT=$(which apt | grep -w "apt" | wc -l) +USES_RPM=$(which rpm | grep -w "rpm" | wc -l) + +if [ $USES_APT -eq 1 ]; then + curl -SLO https://deb.nodesource.com/nsolid_setup_deb.sh + sudo chmod 500 nsolid_setup_deb.sh + sudo ./nsolid_setup_deb.sh 20 + sudo apt-get install nodejs -y + +elif [ $USES_RPM -eq 1 ]; then + curl -SLO https://rpm.nodesource.com/nsolid_setup_rpm.sh + sudo chmod 500 nsolid_setup_rpm.sh + sudo ./nsolid_setup_rpm.sh 20 + sudo yum install nodejs -y --setopt=nodesource-nodejs.module_hotfixes=1 +fi + +echo "Done" +echo "" diff --git a/playwright/staging-tests/package-lock.json b/playwright/staging-tests/package-lock.json new file mode 100644 index 0000000..515588d --- /dev/null +++ b/playwright/staging-tests/package-lock.json @@ -0,0 +1,91 @@ +{ + "name": "staging-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "staging-tests", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.40.1", + "@types/node": "^20.8.9" + } + }, + "node_modules/@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "dependencies": { + "playwright": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@types/node": { + "version": "20.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz", + "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "dependencies": { + "playwright-core": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/playwright/staging-tests/package.json b/playwright/staging-tests/package.json new file mode 100644 index 0000000..a76e840 --- /dev/null +++ b/playwright/staging-tests/package.json @@ -0,0 +1,14 @@ +{ + "name": "staging-tests", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.40.1", + "@types/node": "^20.8.9" + } +} diff --git a/playwright/staging-tests/playwright-path.sh b/playwright/staging-tests/playwright-path.sh new file mode 100755 index 0000000..41a24a0 --- /dev/null +++ b/playwright/staging-tests/playwright-path.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +echo "This script will discover the path to your playwright install" +echo "If you are not in a NixOS environment and it is not installed," +echo "playwright will be installed." +echo "" +echo "At the end of calling this script , you should have a PLAYWRIGHT" +echo "" + +# Are we on nixos or a distro with Nix installed for packages +# Y + # Are we in direnv? + # Y: should all be set up + # N: run nix-shell +#N + # Are we in a deb based distro? + # Are we in an rpm based distro? + # Are we on macOS? + # Are we in windows? + +HAS_PLAYWRIGHT=$(which playwright 2> /dev/null | grep -v "which: no" | wc -l) +PLAYWRIGHT="playwright" +if [ $HAS_PLAYWRIGHT -eq 0 ]; then + PLAYWRIGHT="npx playwright" + + # check if OS is a deb based distro and uses apt + USES_APT=$(which apt 2> /dev/null | grep -w "apt" | wc -l) + # check if OS is an rpm-based distro + USES_RPM=$(which rpm | grep -w "rpm" | wc -l) + + if [ $USES_APT -eq 1 ]; then + # check if nodejs is installed + HAS_NODEJS=$(which node | grep -w "node" | wc -l) + + # if nodejs is present then + if [ $HAS_NODEJS -eq 0 ]; then + source nodesource-install.sh + fi + + # check if npm is present + HAS_NPM=$(which npm | grep -w "npm" | wc -l) + + if [ $HAS_NPM -eq 1 ]; then + NPM="npm" + PLAYWRIGHT_INSTALL=$($NPM ls --depth 1 playwright | grep -w "@playwright/test" | wc -l) + + if [ $PLAYWRIGHT_INSTALL -eq 0 ]; then + $NPM install -D @playwright/test@latest + $NPM ci + $PLAYWRIGHT install --with-deps chromium + fi + + fi + + elif [ $USES_RPM -eq 1 ]; then + + # check if nodejs is installed + HAS_NODEJS=$(which node | grep -w "node" | wc -l) + + # if nodejs is present then + if [ $HAS_NODEJS -eq 0 ]; then + source nodesource-install.sh + fi + + # check if npm is present + HAS_NPM=$(which npm | grep -w "npm" | wc -l) + + if [ $HAS_NPM -eq 1 ]; then + NPM="npm" + PLAYWRIGHT_INSTALL=$($NPM ls --depth 1 playwright | grep -w "@playwright/test" | wc -l) + + if [ $PLAYWRIGHT_INSTALL -eq 0 ]; then + $NPM install -D @playwright/test@latest + $NPM ci + $PLAYWRIGHT install + fi + + fi + fi +fi + +echo "Done." +echo "" diff --git a/playwright/staging-tests/playwright.config.js b/playwright/staging-tests/playwright.config.js new file mode 100644 index 0000000..a09bb2c --- /dev/null +++ b/playwright/staging-tests/playwright.config.js @@ -0,0 +1,83 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* 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: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], + // Use prepared auth state. + storageState: 'auth.json', + }, + // dependencies: ['setup'], + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); + diff --git a/playwright/staging-tests/record-test.sh b/playwright/staging-tests/record-test.sh new file mode 100755 index 0000000..6b4fb74 --- /dev/null +++ b/playwright/staging-tests/record-test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +source base-url.sh + +source playwright-path.sh + +if [ -z "$1" ] +then + echo "Usage: $0 TESTNAME" + echo "e.g. $0 mytest" + echo "will write a new test to tests/mytest.spec.ts" + echo "Do not use spaces in your test name." + echo "" + echo "After recording your test, close the test browser." + echo "You can then run your test by doing:" + echo "npx playwright test tests/mytest.spec.py" + exit +else + echo "Recording test to tests/${1}" +fi + +if [ -w "tests/${1}.spec.ts" ]; then + # File exists and write permission granted to user + # show prompt + echo "File tests/${1}.spec.ts exists. Overwrite? y/n" + read ANSWER + case $ANSWER in + [yY] ) echo "Writing recorded test to tests/${1}.spec.ts" ;; + [nN] ) echo "Cancelled."; exit ;; + esac +fi +TESTNAME=$1 + +$PLAYWRIGHT \ + codegen \ + --target playwright-test \ + --load-storage=auth.json \ + -o tests/$1.spec.ts \ + $BASE_URL + + +echo "Test recording completed." +echo "You can then run your test by doing:" +echo "./run-tests.sh" diff --git a/playwright/staging-tests/run-tests.sh b/playwright/staging-tests/run-tests.sh new file mode 100755 index 0000000..5ff10ab --- /dev/null +++ b/playwright/staging-tests/run-tests.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +source playwright-path.sh + +echo "This script will run the tests defined in tests/" +echo "Before running the tests you need to create the auth config" +echo "" + +$PLAYWRIGHT \ + test \ + --ui \ + --project chromium + +echo "--done--" diff --git a/playwright/staging-tests/shell.nix b/playwright/staging-tests/shell.nix new file mode 100644 index 0000000..595c145 --- /dev/null +++ b/playwright/staging-tests/shell.nix @@ -0,0 +1,28 @@ +let + # + # Note that I am using a snapshot from NixOS unstable here + # so that we can use a more bleeding edge version which includes the test --ui . + # If you want use a different version, go to nix packages search, and find the + # github hash of the version you want to be using, then replace in the URL below. + # + nixpkgs = builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/4059c4f71b3a7339261c0183e365fd8016f24bdb.tar.gz"; + pkgs = import nixpkgs { config = { }; overlays = [ ]; }; +in +with pkgs; +mkShell { + buildInputs = [ + nodejs + playwright-test + # python311Packages.playwright + # python311Packages.pytest + ]; + + PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}"; + + shellHook = '' + # Remove playwright from node_modules, so it will be taken from playwright-test + rm node_modules/@playwright/ -R + export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers} + export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true + ''; +} diff --git a/playwright/staging-tests/tests/test.spec.ts b/playwright/staging-tests/tests/test.spec.ts new file mode 100644 index 0000000..623eb63 --- /dev/null +++ b/playwright/staging-tests/tests/test.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test.use({ + storageState: 'auth.json' +}); + +test('test', async ({ page }) => { + await page.goto('https://example.com/'); + await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible(); + await expect(page.getByText('This domain is for use in')).toBeVisible(); + await page.getByRole('heading', { name: 'Example Domain' }).click(); + await page.getByText('This domain is for use in').click(); + await page.getByRole('link', { name: 'More information...' }).click(); + await page.getByRole('heading', { name: 'Example Domains' }).click(); + await page.getByRole('link', { name: 'Homepage' }).click(); + await page.locator('h1').click(); + await page.getByText('The global coordination of').click(); +}); \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5b1c45a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,21 @@ +#Alternative for pdf creation - broken on nixos and lately ubuntu +mkdocs-with-pdf +# Export to pdf - see https://comwes.github.io/mkpdfs-mkdocs-plugin/index.html +#mkpdfs-mkdocs +# Rather do +# pip install -e git+https://github.com/jwaschkau/mkpdfs-mkdocs-plugin.git#egg=mkpdfs-mkdocs +mkdocs-material +mdx_gh_links +mkdocs-pdf-export-plugin +mkdocstrings-python +mkdocs-video +mkdocs-redirects +mkdocs-enumerate-headings-plugin +mkdocs-git-revision-date-localized-plugin +# needed for the create-uuid.py helper script +shortuuid +# Needed for mkdocstrings python documentation generator +black +cffi +# Needed for PDF back page QR Code +qrcode diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..712eb3d --- /dev/null +++ b/shell.nix @@ -0,0 +1,20 @@ +{ pkgs ? import {}}: + +let + my-python = pkgs.python310; + python-with-my-packages = my-python.withPackages + (p: with p; [ + mkdocs + mkdocs-material + ]); +in pkgs.mkShell { + packages = [ + pkgs.vim + ]; + buildInputs = [ + python-with-my-packages + ]; + shellHook = '' + PYTHONPATH=${python-with-my-packages}/${python-with-my-packages.sitePackages} + ''; +}