WIP: Dodaj wsparcie dla Dockera #128
16
.dockerignore
Normal file
16
.dockerignore
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.log
|
||||||
|
node_modules
|
||||||
|
sidebar.js
|
||||||
|
web-ext-artifacts/
|
||||||
|
lib/*
|
||||||
|
yarn-error.log
|
||||||
|
rentgen.zip
|
||||||
|
|
||||||
|
# Generated PNG icons (build artifacts)
|
||||||
|
assets/icons/*.png
|
||||||
|
assets/icon-addon-*.png
|
||||||
|
|
||||||
|
# Exception: do not ignore the `browser-api` directory inside `lib`
|
||||||
|
!/lib/browser-api/
|
||||||
|
|
||||||
|
Dockerfile
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
sidebar.js
|
sidebar.js
|
||||||
/web-ext-artifacts/
|
/web-ext-artifacts/
|
||||||
|
/artifacts/
|
||||||
lib/*
|
lib/*
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/rentgen.zip
|
/rentgen.zip
|
||||||
@ -12,3 +13,5 @@ lib/*
|
|||||||
|
|
||||||
# Exception: do not ignore the `browser-api` directory inside `lib`
|
# Exception: do not ignore the `browser-api` directory inside `lib`
|
||||||
!/lib/browser-api/
|
!/lib/browser-api/
|
||||||
|
|
||||||
|
.claude
|
||||||
|
|||||||
91
Dockerfile
Normal file
91
Dockerfile
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# Rentgen Browser Extension - Docker Build
|
||||||
|
# See README.md for detailed usage instructions
|
||||||
|
|
||||||
|
# Build stage
|
||||||
|
FROM node:lts AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files for dependency installation (better layer caching)
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# FIXME: COPY . . invalidates cache, so we need to optionally install Firefox
|
||||||
|
# and jump back to the correct stage. It might be too complex though, so we
|
||||||
|
# either need to use build args (if cache properly) or heavily document the
|
||||||
|
# stage transitions, maybe even with a graph.
|
||||||
|
|
||||||
|
# Copy source code (respecting .dockerignore)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the extension for Firefox (default)
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Create the package
|
||||||
|
RUN npm run create-package
|
||||||
|
|
||||||
|
# Code quality stage - for running quality checks
|
||||||
|
FROM builder AS code_quality
|
||||||
|
RUN npm run typecheck && npm run lint
|
||||||
|
|
||||||
|
# Artifacts stage - only contains the built artifacts (for --output)
|
||||||
|
FROM scratch AS artifacts
|
||||||
|
|
||||||
|
# Copy only the built extension zip file to root
|
||||||
|
COPY --from=builder /app/web-ext-artifacts/*.zip /
|
||||||
|
|
||||||
|
# Default stage - full development environment
|
||||||
|
FROM builder
|
||||||
|
|
||||||
|
# Default command shows the built artifact
|
||||||
|
CMD ["ls", "-lh", "/app/web-ext-artifacts/"]
|
||||||
|
|
||||||
|
# Runtime stage - for running extension in Firefox
|
||||||
|
FROM node:lts AS runtime
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built extension from builder
|
||||||
|
COPY --from=builder /app /app
|
||||||
|
|
||||||
|
# Install Firefox and Xvfb for headless execution (cached layer)
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
firefox-esr \
|
||||||
|
xvfb \
|
||||||
|
procps \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python and pip in a separate layer for better caching
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
python3-pip \
|
||||||
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install geckodriver for WebDriver protocol
|
||||||
|
RUN wget -q https://github.com/mozilla/geckodriver/releases/download/v0.34.0/geckodriver-v0.34.0-linux64.tar.gz \
|
||||||
|
&& tar -xzf geckodriver-v0.34.0-linux64.tar.gz \
|
||||||
|
&& mv geckodriver /usr/local/bin/ \
|
||||||
|
&& rm geckodriver-v0.34.0-linux64.tar.gz \
|
||||||
|
&& chmod +x /usr/local/bin/geckodriver
|
||||||
|
|
||||||
|
# Install Python dependencies for testing
|
||||||
|
RUN pip3 install --break-system-packages marionette_driver
|
||||||
|
|
||||||
|
# Set display for Xvfb
|
||||||
|
ENV DISPLAY=:99
|
||||||
|
|
||||||
|
# Start script (not used in verify stage)
|
||||||
|
CMD ["echo", "Use verify stage for testing"]
|
||||||
|
|
||||||
|
# Integration test stage - automated testing with exit code
|
||||||
|
FROM runtime AS integration_test
|
||||||
|
|
||||||
|
# Copy verification scripts
|
||||||
|
COPY tests/test_verify.py /app/tests/test_verify.py
|
||||||
|
COPY tests/test-lib.js /app/tests/test-lib.js
|
||||||
|
RUN chmod +x /app/tests/test_verify.py
|
||||||
|
|
||||||
|
# Run verification and exit with proper exit code
|
||||||
|
CMD ["python3", "/app/tests/test_verify.py"]
|
||||||
@ -1,3 +1,30 @@
|
|||||||
import { init } from "./memory";
|
import { init } from "./memory";
|
||||||
|
|
||||||
|
// Use global browser object directly (available in extension context)
|
||||||
|
declare const browser: any;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
// Test verification handler for Marionette tests
|
||||||
|
// This proves the background script is executing and can communicate with content scripts
|
||||||
|
browser.runtime.onMessage.addListener((message: any, sender: any, sendResponse: any) => {
|
||||||
|
if (message.type === 'RENTGEN_TEST_VERIFICATION') {
|
||||||
|
// Perform a computation to prove the background script is running
|
||||||
|
// This is not just an echo - we're doing actual processing
|
||||||
|
const inputValue = message.inputValue || 0;
|
||||||
|
const computed = (inputValue * 2) + 3;
|
||||||
|
|
||||||
|
// Send back a response with computed value and metadata
|
||||||
|
const response = {
|
||||||
|
success: true,
|
||||||
|
computed: computed,
|
||||||
|
formula: `(${inputValue} * 2) + 3 = ${computed}`,
|
||||||
|
backgroundTimestamp: Date.now(),
|
||||||
|
receivedFrom: message.url || 'unknown',
|
||||||
|
originalInput: inputValue
|
||||||
|
};
|
||||||
|
|
||||||
|
sendResponse(response);
|
||||||
|
return true; // Keep channel open for async response
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import verbs, { v } from './verbs';
|
|||||||
export default function useSurvey(
|
export default function useSurvey(
|
||||||
clusters: RequestCluster[],
|
clusters: RequestCluster[],
|
||||||
{ onComplete }: { onComplete: (sender: { data: RawAnswers }) => void }
|
{ onComplete }: { onComplete: (sender: { data: RawAnswers }) => void }
|
||||||
): Survey.ReactSurveyModel | null {
|
): Survey.Model | null {
|
||||||
const [survey, setSurvey] = React.useState<Survey.Model | null>(null);
|
const [survey, setSurvey] = React.useState<Survey.Model | null>(null);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const model = generateSurveyQuestions(clusters);
|
const model = generateSurveyQuestions(clusters);
|
||||||
|
|||||||
@ -43,7 +43,6 @@ export function StolenData({
|
|||||||
origin={origin}
|
origin={origin}
|
||||||
shorthost={cluster.id}
|
shorthost={cluster.id}
|
||||||
key={cluster.id + origin}
|
key={cluster.id + origin}
|
||||||
refreshToken={eventCounts[cluster.id] || 0}
|
|
||||||
minValueLength={minValueLength}
|
minValueLength={minValueLength}
|
||||||
cookiesOnly={cookiesOnly}
|
cookiesOnly={cookiesOnly}
|
||||||
cookiesOrOriginOnly={cookiesOrOriginOnly}
|
cookiesOrOriginOnly={cookiesOrOriginOnly}
|
||||||
|
|||||||
21
compose.yml
Normal file
21
compose.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
services:
|
||||||
|
rentgen_build:
|
||||||
|
build: .
|
||||||
|
|
||||||
|
rentgen_check:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: code_quality
|
||||||
|
|
||||||
|
rentgen_run:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: runtime
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
|
||||||
|
rentgen_verify:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: integration_test
|
||||||
|
restart: "no"
|
||||||
@ -48,6 +48,7 @@ esbuild
|
|||||||
'components/sidebar/sidebar.tsx',
|
'components/sidebar/sidebar.tsx',
|
||||||
'components/report-window/report-window.tsx',
|
'components/report-window/report-window.tsx',
|
||||||
'background.ts',
|
'background.ts',
|
||||||
|
'tests/test-content-script.js',
|
||||||
'diag.tsx',
|
'diag.tsx',
|
||||||
'styles/global.scss',
|
'styles/global.scss',
|
||||||
'styles/fonts.scss',
|
'styles/fonts.scss',
|
||||||
|
|||||||
@ -13,6 +13,7 @@ function setDomainsCount(counter: number, tabId: number) {
|
|||||||
|
|
||||||
export default class Memory extends SaferEmitter {
|
export default class Memory extends SaferEmitter {
|
||||||
origin_to_history = {} as Record<string, Record<string, RequestCluster>>;
|
origin_to_history = {} as Record<string, Record<string, RequestCluster>>;
|
||||||
|
|
||||||
async register(request: ExtendedRequest) {
|
async register(request: ExtendedRequest) {
|
||||||
await request.init();
|
await request.init();
|
||||||
if (!request.isThirdParty()) {
|
if (!request.isThirdParty()) {
|
||||||
@ -45,7 +46,6 @@ export default class Memory extends SaferEmitter {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
async (request) => {
|
async (request) => {
|
||||||
new ExtendedRequest(request);
|
new ExtendedRequest(request);
|
||||||
|
|||||||
@ -19,7 +19,9 @@
|
|||||||
"create-package:firefox": "web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts",
|
"create-package:firefox": "web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts",
|
||||||
"create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..",
|
"create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "web-ext lint"
|
"lint": "web-ext lint",
|
||||||
|
"docker:verify": "docker compose up --force-recreate --build --abort-on-container-exit --exit-code-from rentgen_verify",
|
||||||
|
"docker:clean": "docker compose down --rmi local --volumes --remove-orphans"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
21
tests/pre-commit
Executable file
21
tests/pre-commit
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Pre-commit hook for Rentgen extension
|
||||||
|
# Builds and runs verification tests before allowing commit
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Running pre-commit checks..."
|
||||||
|
|
||||||
|
# Build all stages
|
||||||
|
echo "Building Docker images..."
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Run code quality checks (typecheck + lint)
|
||||||
|
echo "Running code quality checks..."
|
||||||
|
docker compose up --abort-on-container-exit --exit-code-from rentgen_check rentgen_check
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
echo "Running integration tests..."
|
||||||
|
docker compose up --abort-on-container-exit --exit-code-from rentgen_verify rentgen_verify
|
||||||
|
|
||||||
|
echo "✓ All pre-commit checks passed!"
|
||||||
69
tests/test-content-script.js
Normal file
69
tests/test-content-script.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Test content script - only for automated testing
|
||||||
|
// This script proves bidirectional communication between content script and background
|
||||||
|
|
||||||
|
// Set initial DOM marker to prove content script is injected
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
function setMarker() {
|
||||||
|
if (document.body) {
|
||||||
|
document.body.setAttribute('data-rentgen-injected', 'true');
|
||||||
|
} else {
|
||||||
|
// Wait for DOM ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.body.setAttribute('data-rentgen-injected', 'true');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setMarker();
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Listen for test request from Marionette test script
|
||||||
|
document.addEventListener('rentgen_test_request', async (event) => {
|
||||||
|
try {
|
||||||
|
// Mark that we received the event
|
||||||
|
document.body.setAttribute('data-rentgen-event-received', 'true');
|
||||||
|
|
||||||
|
// Extract test data from event
|
||||||
|
const testData = event.detail || {};
|
||||||
|
const inputValue = testData.value || 42;
|
||||||
|
const timestamp = testData.timestamp || Date.now();
|
||||||
|
|
||||||
|
// Send message to background script and wait for response
|
||||||
|
// This proves background script is running and responsive
|
||||||
|
const response = await browser.runtime.sendMessage({
|
||||||
|
type: 'RENTGEN_TEST_VERIFICATION',
|
||||||
|
inputValue: inputValue,
|
||||||
|
timestamp: timestamp,
|
||||||
|
url: window.location.href,
|
||||||
|
title: document.title
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store the response from background in DOM
|
||||||
|
// This provides undeniable proof of bidirectional communication
|
||||||
|
if (response && response.success) {
|
||||||
|
document.body.setAttribute('data-rentgen-verified', 'true');
|
||||||
|
document.body.setAttribute('data-rentgen-computed', String(response.computed));
|
||||||
|
document.body.setAttribute('data-rentgen-formula', response.formula);
|
||||||
|
document.body.setAttribute('data-rentgen-background-timestamp', String(response.backgroundTimestamp));
|
||||||
|
|
||||||
|
// Also dispatch a custom event with the results
|
||||||
|
document.dispatchEvent(new CustomEvent('rentgen_test_complete', {
|
||||||
|
detail: {
|
||||||
|
success: true,
|
||||||
|
computed: response.computed,
|
||||||
|
formula: response.formula,
|
||||||
|
backgroundTimestamp: response.backgroundTimestamp
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
document.body.setAttribute('data-rentgen-verified', 'false');
|
||||||
|
document.body.setAttribute('data-rentgen-error', 'No response from background');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Store error in DOM for debugging
|
||||||
|
document.body.setAttribute('data-rentgen-verified', 'false');
|
||||||
|
document.body.setAttribute('data-rentgen-error', String(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
58
tests/test-lib.js
Normal file
58
tests/test-lib.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Test library for Marionette-based extension verification
|
||||||
|
// This JavaScript code runs in the browser context via Marionette
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject test content script into the page
|
||||||
|
* @returns {Promise<boolean>} - True if injection successful
|
||||||
|
*/
|
||||||
|
async function injectTestContentScript() {
|
||||||
|
// Read the content script file
|
||||||
|
const response = await fetch(browser.runtime.getURL('lib/tests/test-content-script.js'));
|
||||||
|
const scriptCode = await response.text();
|
||||||
|
|
||||||
|
// Inject it into the page
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.textContent = scriptCode;
|
||||||
|
document.documentElement.appendChild(script);
|
||||||
|
script.remove();
|
||||||
|
|
||||||
|
// Wait a bit for script to initialize
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
return document.body.getAttribute('data-rentgen-injected') === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that background script performs computation correctly
|
||||||
|
* @param {number} testValue - Input value for computation
|
||||||
|
* @returns {Promise<number|null>} - Computed result or null on failure
|
||||||
|
*/
|
||||||
|
async function testBackgroundComputation(testValue) {
|
||||||
|
// Inject content script first
|
||||||
|
const injected = await injectTestContentScript();
|
||||||
|
if (!injected) {
|
||||||
|
return -1; // Content script not loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch test request to content script
|
||||||
|
document.dispatchEvent(new CustomEvent('rentgen_test_request', {
|
||||||
|
detail: { value: testValue, timestamp: Date.now() }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Wait for background response
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let attempts = 0;
|
||||||
|
const checkInterval = setInterval(() => {
|
||||||
|
attempts++;
|
||||||
|
const computed = document.body.getAttribute('data-rentgen-computed');
|
||||||
|
|
||||||
|
if (computed) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
resolve(parseInt(computed));
|
||||||
|
} else if (attempts > 50) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
131
tests/test_verify.py
Executable file
131
tests/test_verify.py
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
test_verify.py - Minimal extension verification test
|
||||||
|
|
||||||
|
Verifies the extension background script is executing by testing
|
||||||
|
bidirectional communication with a simple addition operation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
|
||||||
|
|
||||||
|
def is_tty():
|
||||||
|
"""Check if stdout is a TTY."""
|
||||||
|
return sys.stdout.isatty()
|
||||||
|
|
||||||
|
|
||||||
|
def red(text):
|
||||||
|
"""Return red text if TTY, otherwise plain text."""
|
||||||
|
if is_tty():
|
||||||
|
return f"\033[91m{text}\033[0m"
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def start_xvfb():
|
||||||
|
"""Start Xvfb virtual X server. Returns PID."""
|
||||||
|
xvfb = subprocess.Popen(
|
||||||
|
["Xvfb", ":99", "-screen", "0", "1024x768x24"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
os.environ["DISPLAY"] = ":99"
|
||||||
|
time.sleep(2)
|
||||||
|
return xvfb.pid
|
||||||
|
|
||||||
|
|
||||||
|
def start_webext():
|
||||||
|
"""Start web-ext with Marionette enabled. Returns PID."""
|
||||||
|
webext = subprocess.Popen(
|
||||||
|
["npx", "web-ext", "run",
|
||||||
|
"--arg=-marionette",
|
||||||
|
"--arg=--marionette-port",
|
||||||
|
"--arg=2828"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
return webext.pid
|
||||||
|
|
||||||
|
|
||||||
|
def test_addition():
|
||||||
|
"""Test background script via Marionette. Returns (success, result)."""
|
||||||
|
try:
|
||||||
|
from marionette_driver.marionette import Marionette
|
||||||
|
|
||||||
|
# Wait for Firefox to start
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
# Connect to Marionette
|
||||||
|
client = Marionette(host='localhost', port=2828)
|
||||||
|
client.start_session()
|
||||||
|
|
||||||
|
# Navigate to any page (needed for content script injection)
|
||||||
|
client.navigate("https://example.com")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# Test: background should compute (17 * 2) + 3 = 37
|
||||||
|
test_value = 17
|
||||||
|
expected = 37
|
||||||
|
|
||||||
|
# Load test library
|
||||||
|
test_lib_path = os.path.join(os.path.dirname(__file__), 'test-lib.js')
|
||||||
|
with open(test_lib_path, 'r') as f:
|
||||||
|
test_lib = f.read()
|
||||||
|
|
||||||
|
# Execute test
|
||||||
|
result = client.execute_script(
|
||||||
|
test_lib + "\nreturn testBackgroundComputation(arguments[0]);",
|
||||||
|
script_args=[test_value],
|
||||||
|
script_timeout=10000
|
||||||
|
)
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
if result == expected:
|
||||||
|
return True, expected
|
||||||
|
else:
|
||||||
|
return False, result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup(xvfb_pid, webext_pid):
|
||||||
|
"""Kill processes."""
|
||||||
|
try:
|
||||||
|
os.kill(webext_pid, signal.SIGTERM)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.kill(xvfb_pid, signal.SIGTERM)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main test."""
|
||||||
|
xvfb_pid = start_xvfb()
|
||||||
|
webext_pid = start_webext()
|
||||||
|
|
||||||
|
success, result = test_addition()
|
||||||
|
|
||||||
|
cleanup(xvfb_pid, webext_pid)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print(red(f"FAIL: Expected 37, got {result}"))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(130)
|
||||||
|
except Exception as e:
|
||||||
|
print(red(f"ERROR: {e}"))
|
||||||
|
sys.exit(1)
|
||||||
Loading…
x
Reference in New Issue
Block a user