From a1b43129f1f8de4113c186f57299e59726f52044 Mon Sep 17 00:00:00 2001 From: "Diogo Paulo (dpa)" Date: Fri, 21 Aug 2026 15:09:04 +0100 Subject: [PATCH 1/4] changed the generated package script --- .dockerignore | 19 +++ DOCKER.md | 274 ++++++++++++++++++++++++++++++++++++ DOCKER_QUICK_START.md | 84 +++++++++++ Dockerfile | 54 +++++++ docker-compose.yml | 28 ++++ docker-run.bat | 107 ++++++++++++++ docker-run.sh | 99 +++++++++++++ error.json | 81 +++++++++++ generate_upload_package.ps1 | 29 +++- 9 files changed, 771 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 DOCKER.md create mode 100644 DOCKER_QUICK_START.md create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docker-run.bat create mode 100644 docker-run.sh create mode 100644 error.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..077e4c6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +**/.git +**/.gitignore +**/.vs +**/.vscode +**/bin +**/obj +**/node_modules +**/.DS_Store +README.md +DOCKER.md +.env +.env.local +.editorconfig +.gitattributes +Dockerfile +.dockerignore +docker-compose.yml +docker-run.sh +docker-run.bat diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..f9a9e55 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,274 @@ +# Docker Setup for UltimatePDF-ExternalLogic + +This document describes how to run the UltimatePDF-ExternalLogic project and its tests using Docker. + +## Overview + +The Docker setup includes: +- **Dockerfile**: Multi-stage build using .NET SDK 10.0 for building/testing and aspnet:10.0 for runtime +- **docker-compose.yml**: Orchestrates the application and its services +- **docker-run.sh** / **docker-run.bat**: Helper scripts for common Docker commands + +## Prerequisites + +- Docker Desktop (or Docker daemon running on Linux) +- 4GB+ available RAM for the build +- 2GB+ disk space for Docker images + +## Quick Start + +### Linux/macOS + +```bash +# Build and run tests +./docker-run.sh build +./docker-run.sh test + +# Start the application +./docker-run.sh compose-up + +# View logs +./docker-run.sh logs + +# Stop services +./docker-run.sh compose-down +``` + +### Windows (PowerShell or CMD) + +```powershell +# Build and run tests +.\docker-run.bat build +.\docker-run.bat test + +# Start the application +.\docker-run.bat compose-up + +# View logs +.\docker-run.bat logs + +# Stop services +.\docker-run.bat compose-down +``` + +## Docker Commands + +### Using Helper Scripts + +#### Linux/macOS (`docker-run.sh`) + +```bash +./docker-run.sh build # Build Docker image +./docker-run.sh test # Run unit and integration tests +./docker-run.sh run # Start application container +./docker-run.sh compose-up # Start services with docker-compose +./docker-run.sh compose-down # Stop all services +./docker-run.sh logs # View container logs +./docker-run.sh shell # Open interactive shell in SDK container +./docker-run.sh clean # Remove images and containers +./docker-run.sh help # Show help +``` + +#### Windows (`docker-run.bat`) + +```cmd +docker-run.bat build REM Build Docker image +docker-run.bat test REM Run unit and integration tests +docker-run.bat run REM Start application container +docker-run.bat compose-up REM Start services with docker-compose +docker-run.bat compose-down REM Stop all services +docker-run.bat logs REM View container logs +docker-run.bat shell REM Open interactive shell in SDK container +docker-run.bat clean REM Remove images and containers +docker-run.bat help REM Show help +``` + +### Using Docker Directly + +#### Build the image + +```bash +docker build -t ultimatepdf:latest -f Dockerfile . +``` + +#### Run tests only (without starting the application) + +```bash +docker build -t ultimatepdf:test --target builder -f Dockerfile . +``` + +This will: +- Restore NuGet packages +- Build the solution in Release configuration +- Run unit tests (UnitTests project) +- Run integration tests (IntegrationTests project) +- Build a deployment artifact + +#### Start the application + +```bash +docker run -it --rm \ + -p 5000:80 \ + -e ASPNETCORE_ENVIRONMENT=Development \ + ultimatepdf:latest +``` + +#### Using docker-compose + +```bash +# Start services +docker-compose up -d + +# View logs +docker-compose logs -f ultimatepdf + +# Stop services +docker-compose down + +# Clean up volumes +docker-compose down -v +``` + +## Docker Image Details + +### Build Stage (SDK Image) + +- **Base Image**: `mcr.microsoft.com/dotnet/sdk:10.0` +- **Workdir**: `/src` +- **Operations**: + - Copy source code + - Restore NuGet dependencies + - Build solution in Release mode + - Run unit tests + - Run integration tests (with error tolerance for Chromium dependencies) + - Publish the project + +### Runtime Stage (ASP.NET Image) + +- **Base Image**: `mcr.microsoft.com/dotnet/aspnet:10.0` +- **Workdir**: `/app` +- **Port**: 80 (mapped to host port 5000) +- **Healthcheck**: Basic echo check (customize as needed) +- **Entry Point**: `dotnet UltimatePDF_ExternalLogic.dll` + +## Environment Variables + +- `ASPNETCORE_ENVIRONMENT`: Development (default), Staging, or Production +- `ASPNETCORE_URLS`: http://+:80 (configures HTTP binding) + +## Volumes + +When using docker-compose or manual volume mounting: + +```bash +docker run -v /path/to/src:/src ultimatepdf:latest +``` + +This allows live code changes during development (with proper dotnet watch or manual rebuild). + +## Port Mapping + +- **Host Port**: 5000 +- **Container Port**: 80 +- **Protocol**: HTTP + +Access the application at: `http://localhost:5000` + +## Networking + +When using docker-compose, services are connected via the `ultimatepdf-network` bridge network. + +To connect other containers or services: +```yaml +networks: + - ultimatepdf-network +``` + +## Troubleshooting + +### Build Fails with "Chromium not found" + +The integration tests require Chromium/headless browser dependencies. The Dockerfile includes error tolerance (`|| true`) to continue even if integration tests fail. + +To skip integration tests during build, modify the Dockerfile line: +```dockerfile +RUN dotnet test UltimatePDF_ExternalLogic.IntegrationTests/... || true +``` + +### Port Already in Use + +If port 5000 is already in use, modify `docker-compose.yml`: +```yaml +ports: + - "5001:80" # Map to different host port +``` + +Or specify when running: +```bash +docker run -p 5001:80 ultimatepdf:latest +``` + +### Image Size + +To reduce image size, consider multi-stage builds with separate runtime layers: +- Remove test artifacts from runtime image +- Use `.dockerignore` to exclude unnecessary files (already configured) + +### Development Workflow + +For active development with live reload: + +```bash +# Copy source into container and run tests on change +docker run -it -v $(pwd)/src:/src mcr.microsoft.com/dotnet/sdk:10.0 bash +# Inside container: +cd /src +dotnet watch test UltimatePDF_ExternalLogic.UnitTests +``` + +## Performance Tips + +1. **Layer Caching**: The Dockerfile is optimized for Docker layer caching. NuGet restore is cached unless `*.csproj` files change. + +2. **Build Context**: `.dockerignore` excludes unnecessary files (`bin/`, `obj/`, `.git/`, etc.) to reduce build context. + +3. **Multi-stage Build**: Only the runtime image includes the published artifacts, not all build tools. + +## CI/CD Integration + +### GitHub Actions Example + +```yaml +name: Docker Build and Test + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build and test + run: docker build --target builder -f Dockerfile . +``` + +### GitLab CI Example + +```yaml +build_and_test: + image: docker:latest + services: + - docker:dind + script: + - docker build --target builder -f Dockerfile . +``` + +## Additional Resources + +- [.NET 10.0 Docker Images](https://hub.docker.com/_/microsoft-dotnet) +- [docker-compose Documentation](https://docs.docker.com/compose/) +- [Dockerfile Best Practices](https://docs.docker.com/develop/dev-best-practices/dockerfile_best-practices/) diff --git a/DOCKER_QUICK_START.md b/DOCKER_QUICK_START.md new file mode 100644 index 0000000..d70442c --- /dev/null +++ b/DOCKER_QUICK_START.md @@ -0,0 +1,84 @@ +# Docker Quick Start Guide + +## One-Command Build & Test + +```bash +# Linux/macOS +./docker-run.sh build + +# Windows +.\docker-run.bat build +``` + +This will: +✓ Build the Docker image +✓ Run unit tests +✓ Run integration tests +✓ Publish deployment artifacts + +## Run the Application + +```bash +# Linux/macOS +./docker-run.sh compose-up + +# Windows +.\docker-run.bat compose-up +``` + +Access at: `http://localhost:5000` + +## Useful Commands + +```bash +# Linux/macOS +./docker-run.sh logs # View logs +./docker-run.sh shell # Interactive shell +./docker-run.sh compose-down # Stop services +./docker-run.sh clean # Remove everything + +# Windows +.\docker-run.bat logs +.\docker-run.bat shell +.\docker-run.bat compose-down +.\docker-run.bat clean +``` + +## Manual Docker Commands + +```bash +# Build image +docker build -t ultimatepdf:latest . + +# Run tests only (build stage) +docker build -t ultimatepdf:test --target builder . + +# Start container +docker run -it -p 5000:80 ultimatepdf:latest + +# Use docker-compose +docker-compose up -d +docker-compose logs -f +docker-compose down +``` + +## Troubleshooting + +### Build Fails +- Ensure Docker daemon is running +- Check you have 4GB+ RAM available +- View full logs: `docker build -t ultimatepdf:latest .` + +### Port 5000 Already in Use +- Change in `docker-compose.yml`: `"5001:80"` +- Or use: `docker run -p 5001:80 ultimatepdf:latest` + +### Integration Tests Timeout +- Integration tests require browser resources +- They have error tolerance (`|| true`) and won't block the build + +## See Also + +- `DOCKER.md` - Complete Docker documentation +- `CLAUDE.md` - Project development guide +- `README.md` - UltimatePDF usage and API reference diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..099f4a3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +# Build and test stage using .NET SDK +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS builder + +WORKDIR /src + +# Install Chromium and system dependencies for PuppeteerSharp integration tests +RUN apt-get update && apt-get install -y --no-install-recommends \ + libnspr4 \ + libnss3 \ + libxss1 \ + libappindicator3-1 \ + libsecret-1-0 \ + xdg-utils \ + fonts-liberation \ + libvulkan1 \ + libu2f-udev \ + libgbm1 \ + libdrm2 \ + && rm -rf /var/lib/apt/lists/* + +# Copy solution, project files, and OML files +COPY src/ ./ +COPY oml/ /oml/ + +# Restore dependencies +RUN dotnet restore UltimatePDF_ExternalLogic.sln + +# Build the solution +RUN dotnet build UltimatePDF_ExternalLogic.sln -c Release --no-restore + +# Run unit tests +RUN dotnet test UltimatePDF_ExternalLogic.UnitTests/UltimatePDF_ExternalLogic.UnitTests.csproj -c Release --no-build --logger "console;verbosity=detailed" + +# Run integration tests (with Chromium system dependencies installed) +RUN dotnet test UltimatePDF_ExternalLogic.IntegrationTests/UltimatePDF_ExternalLogic.IntegrationTests.csproj -c Release --no-build --logger "console;verbosity=detailed" + +# Note: E2E tests are skipped as they require a live ODC tenant + +# Publish the main project for deployment +RUN dotnet publish UltimatePDF_ExternalLogic/UltimatePDF_ExternalLogic.csproj -c Release -o /app/publish --no-build + +# Runtime stage using aspnet image +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime + +WORKDIR /app + +# Copy published artifacts from builder +COPY --from=builder /app/publish . + +# Health check (basic - can be customized based on your service) +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD echo "Container is running" + +CMD ["dotnet", "UltimatePDF_ExternalLogic.dll"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3260270 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + ultimatepdf: + build: + context: . + dockerfile: Dockerfile + container_name: ultimatepdf-container + image: ultimatepdf:latest + ports: + - "5000:80" + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://+:80 + volumes: + - ./src:/src + healthcheck: + test: ["CMD", "echo", "Container is running"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + networks: + - ultimatepdf-network + +networks: + ultimatepdf-network: + driver: bridge diff --git a/docker-run.bat b/docker-run.bat new file mode 100644 index 0000000..92d9bc1 --- /dev/null +++ b/docker-run.bat @@ -0,0 +1,107 @@ +@echo off +REM UltimatePDF Docker Build & Test script for Windows + +setlocal enabledelayedexpansion + +echo ====================================== +echo UltimatePDF Docker Build ^& Test +echo ====================================== + +REM Check if Docker is running +docker info >nul 2>&1 +if errorlevel 1 ( + echo Error: Docker daemon is not running + exit /b 1 +) + +set COMMAND=%1 +if "%COMMAND%"=="" set COMMAND=build + +if "%COMMAND%"=="build" ( + echo Building Docker image... + docker build -t ultimatepdf:latest -f Dockerfile . + if errorlevel 1 exit /b 1 + echo Build completed successfully + goto end +) + +if "%COMMAND%"=="test" ( + echo Running tests in container... + docker build -t ultimatepdf:test --target builder -f Dockerfile . + if errorlevel 1 exit /b 1 + echo Tests completed + goto end +) + +if "%COMMAND%"=="run" ( + echo Starting container... + docker run -it --rm ^ + -p 5000:80 ^ + -e ASPNETCORE_ENVIRONMENT=Development ^ + ultimatepdf:latest + goto end +) + +if "%COMMAND%"=="compose-up" ( + echo Starting services with docker-compose... + docker-compose up -d + if errorlevel 1 exit /b 1 + echo Services started + echo Access the application at http://localhost:5000 + goto end +) + +if "%COMMAND%"=="compose-down" ( + echo Stopping services... + docker-compose down + if errorlevel 1 exit /b 1 + echo Services stopped + goto end +) + +if "%COMMAND%"=="logs" ( + echo Showing container logs... + docker-compose logs -f ultimatepdf + goto end +) + +if "%COMMAND%"=="shell" ( + echo Opening shell in container... + docker run -it --rm ^ + -v "%cd%\src:/src" ^ + mcr.microsoft.com/dotnet/sdk:10.0 ^ + powershell + goto end +) + +if "%COMMAND%"=="clean" ( + echo Cleaning up Docker resources... + docker-compose down -v >nul 2>&1 + docker rmi ultimatepdf:latest >nul 2>&1 + docker rmi ultimatepdf:test >nul 2>&1 + echo Cleanup completed + goto end +) + +if "%COMMAND%"=="help" ( + echo Usage: %0 [command] + echo. + echo Commands: + echo build - Build Docker image + echo test - Run tests in container + echo run - Start the application container + echo compose-up - Start services using docker-compose + echo compose-down - Stop services using docker-compose + echo logs - View container logs + echo shell - Open an interactive shell in SDK container + echo clean - Remove Docker images and containers + echo help - Show this help message + goto end +) + +echo Unknown command: %COMMAND% +echo Run "%0 help" for usage information +exit /b 1 + +:end +endlocal diff --git a/docker-run.sh b/docker-run.sh new file mode 100644 index 0000000..82f14dd --- /dev/null +++ b/docker-run.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +set -e + +echo "======================================" +echo "UltimatePDF Docker Build & Test" +echo "======================================" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check if Docker is running +echo "Checking Docker daemon..." +if ! docker info > /dev/null 2>&1; then + echo -e "${RED}Error: Docker daemon is not running${NC}" + exit 1 +fi + +COMMAND=${1:-build} + +case $COMMAND in + build) + echo -e "${YELLOW}Building Docker image...${NC}" + docker build -t ultimatepdf:latest -f Dockerfile . + echo -e "${GREEN}✓ Build completed successfully${NC}" + ;; + + test) + echo -e "${YELLOW}Running tests in container...${NC}" + docker build -t ultimatepdf:test --target builder -f Dockerfile . + echo -e "${GREEN}✓ Tests completed${NC}" + ;; + + run) + echo -e "${YELLOW}Starting container...${NC}" + docker run -it --rm \ + -p 5000:80 \ + -e ASPNETCORE_ENVIRONMENT=Development \ + ultimatepdf:latest + ;; + + compose-up) + echo -e "${YELLOW}Starting services with docker-compose...${NC}" + docker-compose up -d + echo -e "${GREEN}✓ Services started${NC}" + echo "Access the application at http://localhost:5000" + ;; + + compose-down) + echo -e "${YELLOW}Stopping services...${NC}" + docker-compose down + echo -e "${GREEN}✓ Services stopped${NC}" + ;; + + logs) + echo -e "${YELLOW}Showing container logs...${NC}" + docker-compose logs -f ultimatepdf + ;; + + shell) + echo -e "${YELLOW}Opening shell in container...${NC}" + docker run -it --rm \ + -v "$(pwd)/src:/src" \ + mcr.microsoft.com/dotnet/sdk:10.0 \ + bash + ;; + + clean) + echo -e "${YELLOW}Cleaning up Docker resources...${NC}" + docker-compose down -v 2>/dev/null || true + docker rmi ultimatepdf:latest 2>/dev/null || true + docker rmi ultimatepdf:test 2>/dev/null || true + echo -e "${GREEN}✓ Cleanup completed${NC}" + ;; + + help) + echo "Usage: $0 {build|test|run|compose-up|compose-down|logs|shell|clean|help}" + echo "" + echo "Commands:" + echo " build - Build Docker image" + echo " test - Run tests in container" + echo " run - Start the application container" + echo " compose-up - Start services using docker-compose" + echo " compose-down - Stop services using docker-compose" + echo " logs - View container logs" + echo " shell - Open an interactive shell in SDK container" + echo " clean - Remove Docker images and containers" + echo " help - Show this help message" + ;; + + *) + echo -e "${RED}Unknown command: $COMMAND${NC}" + echo "Run '$0 help' for usage information" + exit 1 + ;; +esac diff --git a/error.json b/error.json new file mode 100644 index 0000000..66f398e --- /dev/null +++ b/error.json @@ -0,0 +1,81 @@ +{ + "timestamp": "2026-08-20 17:06:42.070", + "timeEpochMs": 1787242002070, + "timeEpochNs": "1787242002070292300", + "timeLocal": "2026-08-20 17:06:42", + "timeUtc": "2026-08-20 16:06:42", + "timeFromNow": "6 minutes ago", + "logLevel": "error", + "displayLevel": "error", + "line": { + "traceid": "dfbb33313dabed9ea0255b06ca24ed69", + "spanid": "69e25db60e27c88b", + "severity": "Error", + "flags": 1, + "attributes": { + "ActionId": "b9818f56-44f3-4acb-b83f-e3e867fb3644", + "ActionName": "ssUltimatePDFTests.ScreenServices.UltimatePDFTests_MainFlow_Troubleshooting_Controller.ActionPrintToPDF (UltimatePDFTests)", + "Category": "OutSystems.Log.Public", + "ConnectionId": "0HNNUPT4A11GG", + "ParentId": "c67ba3a1b19cc174", + "RequestId": "0HNNUPT4A11GG:00000001", + "RequestPath": "/UltimatePDFTests/screenservices/UltimatePDFTests/MainFlow/Troubleshooting/ActionPrintToPDF", + "SpanId": "69e25db60e27c88b", + "TraceId": "dfbb33313dabed9ea0255b06ca24ed69", + "enduser.id": "", + "exception.inner": "HttpRequestException\tResponse status code does not indicate success: 503 (Service Unavailable).\n at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\n at OutSystems.Application.ExternalLibraries.Services.ExternalLibraryService.CallExecutionEndpointAsync[TRequest,TResponse](String actionKey, TRequest actionInputs, Guid libraryKey, Int32 revision, CancellationToken cancellationToken)\n at OutSystems.NssUltimatePDF_ExternalLogic.CssUltimatePDF_ExternalLogic.MssPrintPDF(String inParamurl, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamviewport, ST_201dce7bb255178c132fec4d547942aeStructure inParamenvironment, RL_b1252de0322ec8ed93ca816f48332980 inParamcookies, ST_8425f751b288b04b92b7676e815d0bf2Structure inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, ST_578e7c8738096bb2afd62646bb6dafdcStructure inParamdocumentProperties, CancellationToken cancellationToken)\n at ssUltimatePDF.RssExternalLibraryUltimatePDF_ExternalLogic.MssPrintPDF(IRequestContext requestContext, String inParamurl, IRecord inParamviewport, IRecord inParamenvironment, IRecordList inParamcookies, IRecord inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, IRecord inParamdocumentProperties, CancellationToken cancellationToken)", + "exception.message": "Something went wrong on our side.", + "exception.stacktrace": " at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\n at OutSystems.Application.ExternalLibraries.Services.ExternalLibraryService.CallExecutionEndpointAsync[TRequest,TResponse](String actionKey, TRequest actionInputs, Guid libraryKey, Int32 revision, CancellationToken cancellationToken)\n at OutSystems.NssUltimatePDF_ExternalLogic.CssUltimatePDF_ExternalLogic.MssPrintPDF(String inParamurl, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamviewport, ST_201dce7bb255178c132fec4d547942aeStructure inParamenvironment, RL_b1252de0322ec8ed93ca816f48332980 inParamcookies, ST_8425f751b288b04b92b7676e815d0bf2Structure inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, ST_578e7c8738096bb2afd62646bb6dafdcStructure inParamdocumentProperties, CancellationToken cancellationToken)\n at ssUltimatePDF.RssExternalLibraryUltimatePDF_ExternalLogic.MssPrintPDF(IRequestContext requestContext, String inParamurl, IRecord inParamviewport, IRecord inParamenvironment, IRecordList inParamcookies, IRecord inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, IRecord inParamdocumentProperties, CancellationToken cancellationToken) On \n at ssUltimatePDF.RssExternalLibraryUltimatePDF_ExternalLogic.MssPrintPDF(IRequestContext requestContext, String inParamurl, IRecord inParamviewport, IRecord inParamenvironment, IRecordList inParamcookies, IRecord inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, IRecord inParamdocumentProperties, CancellationToken cancellationToken)\n at ssUltimatePDF.Actions.ActionPrintPDF(IRequestContext requestContext, String inParamurl, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamviewport, ST_201dce7bb255178c132fec4d547942aeStructure inParamenvironment, RL_b1252de0322ec8ed93ca816f48332980 inParamcookies, ST_8425f751b288b04b92b7676e815d0bf2Structure inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, ST_578e7c8738096bb2afd62646bb6dafdcStructure inParamdocumentProperties, CancellationToken cancellationToken)\n at ssUltimatePDF.Actions.ActionDEPRECATED_PrintToPDF_Advanced(IRequestContext requestContext, String inParamURL, ST_201dce7bb255178c132fec4d547942aeStructure inParamEnvironment, String inParamPaperSize, String inParamMarginSize, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamViewport, Boolean inParamCollectLogs, RL_b1252de0322ec8ed93ca816f48332980 inParamCookies, Int32 inParamTimeoutSeconds, ST_678e02010e9c185764dde2baed007511Structure inParamRestCaller, Boolean inParamAttachFilesLogs, CancellationToken cancellationToken)\n at ssUltimatePDF.Actions.ActionPrintToPDF(IRequestContext requestContext, String inParamURL, ST_201dce7bb255178c132fec4d547942aeStructure inParamEnvironment, CancellationToken cancellationToken)\n at ssUltimatePDFTests.RsseSpaceUltimatePDF.MssPrintToPDF(IRequestContext requestContext, String inParamURL, IRecord inParamEnvironment, CancellationToken cancellationToken)\n at ssUltimatePDFTests.Actions.ActionPrintToPDF(IRequestContext requestContext, String inParamURL, ST_201dce7bb255178c132fec4d547942aeStructure inParamEnvironment, CancellationToken cancellationToken)\n at ssUltimatePDFTests.ScreenServices.UltimatePDFTests_MainFlow_Troubleshooting_Controller.b__11_0(String screenName, JObject screenModel, JObject inputParameters, JObject clientVariables, CancellationToken cancellationToken)\n at OutSystems.RESTService.Runtime.Core.Controllers.ScreenServices.ScreenServicesApiController.InnerEndpointAsync(String apiVersionHash, EndpointAsyncImplementationDelegate implementationAsync, ResponseVersionInfo responseVersionInfo, ServiceRequest requestPayload, Activity activity, CancellationToken cancellationToken)\n at OutSystems.RESTService.Runtime.Core.Controllers.ScreenServices.ScreenServicesApiController.EndpointAsync(Stream input, String apiVersionHash, EndpointAsyncImplementationDelegate implementationAsync, CancellationToken cancellationToken)", + "exception.type": "OutSystems.Application.ErrorHandling.ExtensionException", + "outsystems.error.code": "OS-BERT-SLIB-00000", + "outsystems.exception": "{\"type\":\"OutSystems.Application.ErrorHandling.ExtensionException\",\"message\":\"Something went wrong on our side.\",\"stacktrace\":\" at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\\n at OutSystems.Application.ExternalLibraries.Services.ExternalLibraryService.CallExecutionEndpointAsync[TRequest,TResponse](String actionKey, TRequest actionInputs, Guid libraryKey, Int32 revision, CancellationToken cancellationToken)\\n at OutSystems.NssUltimatePDF_ExternalLogic.CssUltimatePDF_ExternalLogic.MssPrintPDF(String inParamurl, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamviewport, ST_201dce7bb255178c132fec4d547942aeStructure inParamenvironment, RL_b1252de0322ec8ed93ca816f48332980 inParamcookies, ST_8425f751b288b04b92b7676e815d0bf2Structure inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, ST_578e7c8738096bb2afd62646bb6dafdcStructure inParamdocumentProperties, CancellationToken cancellationToken)\\n at ssUltimatePDF.RssExternalLibraryUltimatePDF_ExternalLogic.MssPrintPDF(IRequestContext requestContext, String inParamurl, IRecord inParamviewport, IRecord inParamenvironment, IRecordList inParamcookies, IRecord inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, IRecord inParamdocumentProperties, CancellationToken cancellationToken) On \\n at ssUltimatePDF.RssExternalLibraryUltimatePDF_ExternalLogic.MssPrintPDF(IRequestContext requestContext, String inParamurl, IRecord inParamviewport, IRecord inParamenvironment, IRecordList inParamcookies, IRecord inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, IRecord inParamdocumentProperties, CancellationToken cancellationToken)\\n at ssUltimatePDF.Actions.ActionPrintPDF(IRequestContext requestContext, String inParamurl, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamviewport, ST_201dce7bb255178c132fec4d547942aeStructure inParamenvironment, RL_b1252de0322ec8ed93ca816f48332980 inParamcookies, ST_8425f751b288b04b92b7676e815d0bf2Structure inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, ST_578e7c8738096bb2afd62646bb6dafdcStructure inParamdocumentProperties, CancellationToken cancellationToken)\\n at ssUltimatePDF.Actions.ActionDEPRECATED_PrintToPDF_Advanced(IRequestContext requestContext, String inParamURL, ST_201dce7bb255178c132fec4d547942aeStructure inParamEnvironment, String inParamPaperSize, String inParamMarginSize, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamViewport, Boolean inParamCollectLogs, RL_b1252de0322ec8ed93ca816f48332980 inParamCookies, Int32 inParamTimeoutSeconds, ST_678e02010e9c185764dde2baed007511Structure inParamRestCaller, Boolean inParamAttachFilesLogs, CancellationToken cancellationToken)\\n at ssUltimatePDF.Actions.ActionPrintToPDF(IRequestContext requestContext, String inParamURL, ST_201dce7bb255178c132fec4d547942aeStructure inParamEnvironment, CancellationToken cancellationToken)\\n at ssUltimatePDFTests.RsseSpaceUltimatePDF.MssPrintToPDF(IRequestContext requestContext, String inParamURL, IRecord inParamEnvironment, CancellationToken cancellationToken)\\n at ssUltimatePDFTests.Actions.ActionPrintToPDF(IRequestContext requestContext, String inParamURL, ST_201dce7bb255178c132fec4d547942aeStructure inParamEnvironment, CancellationToken cancellationToken)\\n at ssUltimatePDFTests.ScreenServices.UltimatePDFTests_MainFlow_Troubleshooting_Controller.b__11_0(String screenName, JObject screenModel, JObject inputParameters, JObject clientVariables, CancellationToken cancellationToken)\\n at OutSystems.RESTService.Runtime.Core.Controllers.ScreenServices.ScreenServicesApiController.InnerEndpointAsync(String apiVersionHash, EndpointAsyncImplementationDelegate implementationAsync, ResponseVersionInfo responseVersionInfo, ServiceRequest requestPayload, Activity activity, CancellationToken cancellationToken)\\n at OutSystems.RESTService.Runtime.Core.Controllers.ScreenServices.ScreenServicesApiController.EndpointAsync(Stream input, String apiVersionHash, EndpointAsyncImplementationDelegate implementationAsync, CancellationToken cancellationToken)\",\"inner\":[{\"type\":\"System.Net.Http.HttpRequestException\",\"message\":\"Response status code does not indicate success: 503 (Service Unavailable).\",\"stacktrace\":\" at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\\n at OutSystems.Application.ExternalLibraries.Services.ExternalLibraryService.CallExecutionEndpointAsync[TRequest,TResponse](String actionKey, TRequest actionInputs, Guid libraryKey, Int32 revision, CancellationToken cancellationToken)\\n at OutSystems.NssUltimatePDF_ExternalLogic.CssUltimatePDF_ExternalLogic.MssPrintPDF(String inParamurl, ST_5955a84215da631b6940d5eb4f1c9ae0Structure inParamviewport, ST_201dce7bb255178c132fec4d547942aeStructure inParamenvironment, RL_b1252de0322ec8ed93ca816f48332980 inParamcookies, ST_8425f751b288b04b92b7676e815d0bf2Structure inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, ST_578e7c8738096bb2afd62646bb6dafdcStructure inParamdocumentProperties, CancellationToken cancellationToken)\\n at ssUltimatePDF.RssExternalLibraryUltimatePDF_ExternalLogic.MssPrintPDF(IRequestContext requestContext, String inParamurl, IRecord inParamviewport, IRecord inParamenvironment, IRecordList inParamcookies, IRecord inParampaper, Int32 inParamtimeoutSeconds, Boolean inParamcollectLogs, Boolean inParamattachFilesLogs, IRecord inParamdocumentProperties, CancellationToken cancellationToken)\"}]}", + "outsystems.log.message.tag": "Screen Services", + "outsystems.log.message.timestamp": "2026-08-20 16:06:42:069" + }, + "resources": { + "k8s.container.name": "fc73e17a-2326-4053-9b35-435ec36af551", + "k8s.pod.name": "fc73e17a-2326-4053-9b35-435ec36af551-dc9d94c59-zb6h4", + "k8s.pod.uid": "89c0e13b-d683-4aeb-9149-2b6e98780c24", + "otel.collector.version": "v2", + "outsystems.app.key": "fc73e17a-2326-4053-9b35-435ec36af551", + "outsystems.app.name": "Ultimate PDF Tests", + "outsystems.app.revision": "5", + "outsystems.app.version": "5", + "outsystems.app.versiontoken": "SBIoOC7IsjdBuF+JN3NcNg", + "outsystems.asset.key": "fc73e17a-2326-4053-9b35-435ec36af551", + "outsystems.asset.name": "Ultimate PDF Tests", + "outsystems.asset.revision": "5", + "outsystems.asset.type": "app", + "outsystems.asset.versiontoken": "SBIoOC7IsjdBuF+JN3NcNg", + "outsystems.compiler.version": "v18.529.7", + "outsystems.env.key": "d99806f2-bd1c-4d82-b6e0-c1caee5e8ed6", + "outsystems.service.type": "Runtime", + "outsystems.tenant.key": "643cd79e-4470-4db8-897b-ce9d0877937c", + "outsystems.worker.version": "v18.529.7", + "outsystems.workload.type": "app", + "service.version": "12.51.161" + }, + "instrumentation_scope": { + "name": "OutSystems.Log.Public" + } + }, + "labels": { + "Indexed labels": { + "cluster_id": "06610df7-6606-4a0c-aec0-7b1de3c56d86", + "component": "runtime", + "exporter": "OTLP", + "job": "BackendRuntime", + "k8s_namespace_name": "d99806f2-bd1c-4d82-b6e0-c1caee5e8ed6", + "level": "ERROR", + "outsystems_otel_access_type": "3", + "outsystems_otel_access_visibility": "2", + "ring": "test", + "service_name": "BackendRuntime", + "stamp": "runtime-test-sh-06610df7-6606-4a0c-aec0-7b1de3c56d86", + "tenant_id": "643cd79e-4470-4db8-897b-ce9d0877937c" + }, + "Structured metadata": { + "detected_level": "error" + } + } +} diff --git a/generate_upload_package.ps1 b/generate_upload_package.ps1 index 490dae1..828e328 100644 --- a/generate_upload_package.ps1 +++ b/generate_upload_package.ps1 @@ -1,6 +1,27 @@ -if (Test-Path -Path .\UltimatePDF_ExternalLogic.zip -PathType Leaf) { - Remove-Item -Path .\UltimatePDF_ExternalLogic.zip -Force +$zipPath = ".\UltimatePDF_ExternalLogic.zip" +$projectPath = "src\UltimatePDF_ExternalLogic\UltimatePDF_ExternalLogic.csproj" +$publishDir = ".\src\UltimatePDF_ExternalLogic\bin\Release\net10.0\linux-x64\publish\" + +if (Test-Path -Path $zipPath -PathType Leaf) { + Write-Host "Removing existing package: $zipPath" + Remove-Item -Path $zipPath -Force } + +Write-Host "Setting execution policy for current user (Unrestricted)..." Set-ExecutionPolicy -Scope CurrentUser Unrestricted -dotnet publish src\UltimatePDF_ExternalLogic.sln -c Release -r linux-x64 --self-contained false -Compress-Archive -Path .\src\UltimatePDF_ExternalLogic\bin\Release\net10.0\linux-x64\publish\* -Update -DestinationPath UltimatePDF_ExternalLogic.zip \ No newline at end of file + +# Only the main project is needed to produce the upload package (test projects aren't part of it). +Write-Host "Publishing $projectPath for linux-x64 (Release, framework-dependent)..." +dotnet publish $projectPath -c Release -r linux-x64 --self-contained false + +# dotnet publish doesn't throw in PowerShell on failure, so without this check a failed build +# would silently get packaged from stale (or missing) publish output. +if ($LASTEXITCODE -ne 0) { + Write-Host "dotnet publish failed with exit code $LASTEXITCODE. Aborting package creation." -ForegroundColor Red + exit $LASTEXITCODE +} + +Write-Host "Compressing publish output into $zipPath..." +Compress-Archive -Path (Join-Path $publishDir '*') -Update -DestinationPath $zipPath + +Write-Host "Package created: $zipPath" -ForegroundColor Green \ No newline at end of file From f3ef1577147da671eb5be8b2c2d3d4d14bc61e81 Mon Sep 17 00:00:00 2001 From: "Diogo Paulo (dpa)" Date: Fri, 21 Aug 2026 15:11:35 +0100 Subject: [PATCH 2/4] changed namespace strucutre to a .net 10 syntax --- .../BrowserExecution/BrowserInstancePool.cs | 53 +- .../ODCUltimatePDFExecutionContext.cs | 311 ++++++----- .../BrowserExecution/PooledBrowserInstance.cs | 19 +- .../BrowserExecution/PooledPage.cs | 39 +- .../Cleanup/AbstractCleanupTask.cs | 19 +- .../Cleanup/BrowserInstanceCleanup.cs | 27 +- .../IUltimatePDF_ExternalLogic.cs | 227 ++++---- .../LayoutPrintPipeline/LayoutPrint.cs | 167 +++--- .../LayoutPrintPipeline/Pipeline.cs | 363 +++++++------ .../LayoutPrintPipeline/PrintSection.cs | 38 +- .../Management/Troubleshooting/Logger.cs | 337 ++++++------ .../Structures/Cookie.cs | 37 +- .../Structures/DocumentProperties.cs | 153 +++--- .../Structures/Environment.cs | 39 +- .../Structures/Paper.cs | 105 ++-- .../Structures/RestCaller.cs | 73 ++- .../Structures/S3Endpoints.cs | 29 +- .../Structures/ScreenshotOptions.cs | 19 +- .../Structures/Viewport.cs | 29 +- .../UltimatePDF_ExternalLogic.cs | 495 +++++++++--------- .../Utils/AsyncUtils.cs | 27 +- .../Utils/PDFMetadataUtil.cs | 117 ++--- .../Utils/PNGMetadataUtil.cs | 187 ++++--- .../Utils/PngEncoding.cs | 77 ++- .../Utils/ResourceAccessor.cs | 19 +- .../Utils/RestSender.cs | 73 ++- .../Utils/S3Sender.cs | 75 ++- .../Utils/UrlUtils.cs | 95 ++-- 28 files changed, 1611 insertions(+), 1638 deletions(-) diff --git a/src/UltimatePDF_ExternalLogic/BrowserExecution/BrowserInstancePool.cs b/src/UltimatePDF_ExternalLogic/BrowserExecution/BrowserInstancePool.cs index bdc6274..1ffb25e 100644 --- a/src/UltimatePDF_ExternalLogic/BrowserExecution/BrowserInstancePool.cs +++ b/src/UltimatePDF_ExternalLogic/BrowserExecution/BrowserInstancePool.cs @@ -7,41 +7,40 @@ using PuppeteerSharp; using UltimatePDF_ExternalLogic.Utils; -namespace OutSystems.UltimatePDF_ExternalLogic.BrowserExecution { - public class BrowserInstancePool { +namespace OutSystems.UltimatePDF_ExternalLogic.BrowserExecution; +public class BrowserInstancePool { - private readonly SemaphoreSlim mutex = new(1, 1); + private readonly SemaphoreSlim mutex = new(1, 1); - private static readonly List pool = new(); + private static readonly List pool = new(); - public BrowserInstancePool() { - } - - private async Task NewBrowserInstance(Logger logger) { - await mutex.WaitAsync(); - try { - var instance = pool.FirstOrDefault(i => i.IsHealthy); + public BrowserInstancePool() { + } - if (instance == null) { - logger.Log("Create new Browser Instance"); + private async Task NewBrowserInstance(Logger logger) { + await mutex.WaitAsync(); + try { + var instance = pool.FirstOrDefault(i => i.IsHealthy); - var browserLauncher = new HeadlessChromiumPuppeteerLauncher(logger.GetLoggerFactory("browser.txt")); - var browser = await browserLauncher.LaunchAsync(); - instance = new PooledBrowserInstance(browser); - pool.Add(instance); - } + if (instance == null) { + logger.Log("Create new Browser Instance"); - return instance; - } finally { - mutex.Release(); + var browserLauncher = new HeadlessChromiumPuppeteerLauncher(logger.GetLoggerFactory("browser.txt")); + var browser = await browserLauncher.LaunchAsync(); + instance = new PooledBrowserInstance(browser); + pool.Add(instance); } - } - public async Task NewPooledPage(Logger logger) { - var instance = await NewBrowserInstance(logger); - var page = await instance.Browser.NewPageAsync(); - var pooledPage = new PooledPage(page, logger); - return pooledPage; + return instance; + } finally { + mutex.Release(); } } + + public async Task NewPooledPage(Logger logger) { + var instance = await NewBrowserInstance(logger); + var page = await instance.Browser.NewPageAsync(); + var pooledPage = new PooledPage(page, logger); + return pooledPage; + } } diff --git a/src/UltimatePDF_ExternalLogic/BrowserExecution/ODCUltimatePDFExecutionContext.cs b/src/UltimatePDF_ExternalLogic/BrowserExecution/ODCUltimatePDFExecutionContext.cs index 506e3d3..bc23937 100644 --- a/src/UltimatePDF_ExternalLogic/BrowserExecution/ODCUltimatePDFExecutionContext.cs +++ b/src/UltimatePDF_ExternalLogic/BrowserExecution/ODCUltimatePDFExecutionContext.cs @@ -10,202 +10,201 @@ using PuppeteerSharp; using PuppeteerSharp.Media; -namespace OutSystems.UltimatePDF_ExternalLogic.BrowserExecution { - internal class UltimatePDFExecutionContext { +namespace OutSystems.UltimatePDF_ExternalLogic.BrowserExecution; +internal class UltimatePDFExecutionContext { - private const string USER_AGENT_SUFFIX = "UltimatePDF/1.0"; + private const string USER_AGENT_SUFFIX = "UltimatePDF/1.0"; - private static readonly BrowserInstancePool pool = new(); + private static readonly BrowserInstancePool pool = new(); - public async static Task PrintPDF( - Uri uri, string baseUrl, string locale, string timezone, IEnumerable cookies, - ViewPortOptions viewport, PdfOptions options, int timeoutSeconds, Logger logger) { + public async static Task PrintPDF( + Uri uri, string baseUrl, string locale, string timezone, IEnumerable cookies, + ViewPortOptions viewport, PdfOptions options, int timeoutSeconds, Logger logger) { - logger.Log("Page open... " + uri); + logger.Log("Page open... " + uri); - Stopwatch sw = new(); - sw.Start(); + Stopwatch sw = new(); + sw.Start(); - using var pooled = await pool.NewPooledPage(logger); - await SetupPage(pooled.Page, uri, viewport, locale, timezone, cookies, - timeoutSeconds, logger, baseUrl); + using var pooled = await pool.NewPooledPage(logger); + await SetupPage(pooled.Page, uri, viewport, locale, timezone, cookies, + timeoutSeconds, logger, baseUrl); - logger.Log("Page opened in " + sw.ElapsedMilliseconds + "ms"); - await pooled.Page.WaitForSelectorAsync(":root:not(.ultimate-pdf-is-not-ready)"); - logger.Log("Page opened and ready in " + sw.ElapsedMilliseconds + "ms"); + logger.Log("Page opened in " + sw.ElapsedMilliseconds + "ms"); + await pooled.Page.WaitForSelectorAsync(":root:not(.ultimate-pdf-is-not-ready)"); + logger.Log("Page opened and ready in " + sw.ElapsedMilliseconds + "ms"); - if (!string.IsNullOrEmpty(baseUrl)) { - await pooled.Page.EvaluateExpressionAsync("window?.UltimatePDF?.setBaseUrl?.('" + HttpUtility.JavaScriptStringEncode(baseUrl) + "')"); - } - - if (logger.IsEnabled) { - string html = await pooled.Page.GetContentAsync(); - logger.Attach("input.html", Encoding.UTF8.GetBytes(html)); - } + if (!string.IsNullOrEmpty(baseUrl)) { + await pooled.Page.EvaluateExpressionAsync("window?.UltimatePDF?.setBaseUrl?.('" + HttpUtility.JavaScriptStringEncode(baseUrl) + "')"); + } - Pipeline pipeline = new Pipeline(); - await pipeline.Initialize(pooled.Page); - byte[] pdf; + if (logger.IsEnabled) { + string html = await pooled.Page.GetContentAsync(); + logger.Attach("input.html", Encoding.UTF8.GetBytes(html)); + } - if (pipeline.HasLayouts) { - logger.Log("Using UltimatePDF layout pipeline"); + Pipeline pipeline = new Pipeline(); + await pipeline.Initialize(pooled.Page); + byte[] pdf; - pdf = await pipeline.Render(pooled.Page, logger); - logger.Attach("output.pdf", pdf); - } else { - await InjectCustomStylesAsync(pooled.Page, ref options); - pdf = await pooled.Page.PdfDataAsync(options); - logger.Attach("output.pdf", pdf); - } + if (pipeline.HasLayouts) { + logger.Log("Using UltimatePDF layout pipeline"); - await pooled.Page.CloseAsync(); - await pooled.Page.Browser.CloseAsync(); - - return pdf; + pdf = await pipeline.Render(pooled.Page, logger); + logger.Attach("output.pdf", pdf); + } else { + await InjectCustomStylesAsync(pooled.Page, ref options); + pdf = await pooled.Page.PdfDataAsync(options); + logger.Attach("output.pdf", pdf); } - public async static Task ScreenshotPNG( - Uri uri, string baseUrl, string locale, string timezone, IEnumerable cookies, - ViewPortOptions viewport, ScreenshotOptions options, int timeoutSeconds, Logger logger) { + await pooled.Page.CloseAsync(); + await pooled.Page.Browser.CloseAsync(); - logger.Log("Page open..."); + return pdf; + } - var sw = new Stopwatch(); - sw.Start(); + public async static Task ScreenshotPNG( + Uri uri, string baseUrl, string locale, string timezone, IEnumerable cookies, + ViewPortOptions viewport, ScreenshotOptions options, int timeoutSeconds, Logger logger) { - using var pooled = await pool.NewPooledPage(logger); - await SetupPage(pooled.Page, uri, viewport, locale, timezone, cookies, timeoutSeconds, logger, baseUrl); + logger.Log("Page open..."); - if (logger.IsEnabled) { - string html = await pooled.Page.GetContentAsync(); - logger.Attach("input.html", Encoding.UTF8.GetBytes(html)); - } + var sw = new Stopwatch(); + sw.Start(); - byte[] png = await pooled.Page.ScreenshotDataAsync(options); - logger.Attach("output.png", png); + using var pooled = await pool.NewPooledPage(logger); + await SetupPage(pooled.Page, uri, viewport, locale, timezone, cookies, timeoutSeconds, logger, baseUrl); - return png; + if (logger.IsEnabled) { + string html = await pooled.Page.GetContentAsync(); + logger.Attach("input.html", Encoding.UTF8.GetBytes(html)); } - private static async Task SetupPage( - IPage page, Uri uri, ViewPortOptions viewport, string locale, string timezone, - IEnumerable cookies, int timeout, Logger logger, string baseUrl) { + byte[] png = await pooled.Page.ScreenshotDataAsync(options); + logger.Attach("output.png", png); - await page.SetViewportAsync(viewport); + return png; + } - string originalUserAgent = await page.Browser.GetUserAgentAsync(); - await page.SetUserAgentAsync($"{originalUserAgent} {USER_AGENT_SUFFIX}"); + private static async Task SetupPage( + IPage page, Uri uri, ViewPortOptions viewport, string locale, string timezone, + IEnumerable cookies, int timeout, Logger logger, string baseUrl) { - if (!string.IsNullOrEmpty(locale)) { - await page.SetExtraHttpHeadersAsync(GetLocaleHeaders(locale)); - await page.EvaluateExpressionOnNewDocumentAsync(GetLocaleExpressionToEvaluate(locale)); - } + await page.SetViewportAsync(viewport); - if (!string.IsNullOrEmpty(timezone)) { - await page.EmulateTimezoneAsync(timezone); - } + string originalUserAgent = await page.Browser.GetUserAgentAsync(); + await page.SetUserAgentAsync($"{originalUserAgent} {USER_AGENT_SUFFIX}"); - if (cookies.Any()) { - await page.SetCookieAsync(cookies.ToArray()); - } + if (!string.IsNullOrEmpty(locale)) { + await page.SetExtraHttpHeadersAsync(GetLocaleHeaders(locale)); + await page.EvaluateExpressionOnNewDocumentAsync(GetLocaleExpressionToEvaluate(locale)); + } - var navigationOptions = new NavigationOptions() { - WaitUntil = new WaitUntilNavigation[] { - WaitUntilNavigation.DOMContentLoaded, - WaitUntilNavigation.Load, - WaitUntilNavigation.Networkidle0 - } - }; + if (!string.IsNullOrEmpty(timezone)) { + await page.EmulateTimezoneAsync(timezone); + } - if (timeout > 0) { - navigationOptions.Timeout = timeout * 1000; - } + if (cookies.Any()) { + await page.SetCookieAsync(cookies.ToArray()); + } - lock (page.Browser) { - logger.Log($"Go to... {uri}"); - page.GoToAsync(uri.ToString(), navigationOptions).Wait(); + var navigationOptions = new NavigationOptions() { + WaitUntil = new WaitUntilNavigation[] { + WaitUntilNavigation.DOMContentLoaded, + WaitUntilNavigation.Load, + WaitUntilNavigation.Networkidle0 } + }; - if (!string.IsNullOrEmpty(locale)) { - logger.Log($"Set locale to {locale}"); - await page.EvaluateExpressionAsync("window?.UltimatePDF?.runtime?.setLocale?.('" + HttpUtility.JavaScriptStringEncode(locale) + "')"); - } + if (timeout > 0) { + navigationOptions.Timeout = timeout * 1000; + } - // used for the Async Screen client actions - await page.WaitForSelectorAsync(":root:not(.ultimate-pdf-is-not-ready)"); + lock (page.Browser) { + logger.Log($"Go to... {uri}"); + page.GoToAsync(uri.ToString(), navigationOptions).Wait(); + } - if (!string.IsNullOrEmpty(baseUrl)) { - logger.Log($"Set base url to {baseUrl}"); - await page.EvaluateExpressionAsync("window?.UltimatePDF?.setBaseUrl?.('" + HttpUtility.JavaScriptStringEncode(baseUrl) + "')"); - } + if (!string.IsNullOrEmpty(locale)) { + logger.Log($"Set locale to {locale}"); + await page.EvaluateExpressionAsync("window?.UltimatePDF?.runtime?.setLocale?.('" + HttpUtility.JavaScriptStringEncode(locale) + "')"); } - private static Dictionary GetLocaleHeaders(string locale) { - var headers = new Dictionary(); + // used for the Async Screen client actions + await page.WaitForSelectorAsync(":root:not(.ultimate-pdf-is-not-ready)"); - int separatorIndex = locale.IndexOf("-"); - if (separatorIndex > 0) { - headers.Add("Accept-Language", string.Concat(locale, ",", locale[0..separatorIndex])); - } else { - headers.Add("Accept-Language", locale); - } + if (!string.IsNullOrEmpty(baseUrl)) { + logger.Log($"Set base url to {baseUrl}"); + await page.EvaluateExpressionAsync("window?.UltimatePDF?.setBaseUrl?.('" + HttpUtility.JavaScriptStringEncode(baseUrl) + "')"); + } + } + + private static Dictionary GetLocaleHeaders(string locale) { + var headers = new Dictionary(); + + int separatorIndex = locale.IndexOf("-"); + if (separatorIndex > 0) { + headers.Add("Accept-Language", string.Concat(locale, ",", locale[0..separatorIndex])); + } else { + headers.Add("Accept-Language", locale); + } - return headers; - } - - private static string GetLocaleExpressionToEvaluate(string locale) { - return @" - Object.defineProperty(navigator, 'language', { - get: function() { return '" + HttpUtility.JavaScriptStringEncode(locale) + @"'; } - }); - Object.defineProperty(navigator, 'languages', { - get: function() { - var separatorIndex = this.language.indexOf('-'); - if (separatorIndex > 0) { - return [ this.language, this.language.substr(0, separatorIndex) ]; - } else { - return [ this.language ]; - } + return headers; + } + + private static string GetLocaleExpressionToEvaluate(string locale) { + return @" + Object.defineProperty(navigator, 'language', { + get: function() { return '" + HttpUtility.JavaScriptStringEncode(locale) + @"'; } + }); + Object.defineProperty(navigator, 'languages', { + get: function() { + var separatorIndex = this.language.indexOf('-'); + if (separatorIndex > 0) { + return [ this.language, this.language.substr(0, separatorIndex) ]; + } else { + return [ this.language ]; } - }); - "; - } - - private static Task InjectCustomStylesAsync(IPage page, ref PdfOptions options) { - /* - * It seems that Puppeteer is not overriding the page styles from the print stylesheet. - * As a workaround, we inject a