Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8f502b8
Added downloaded pdf report verify
can-angun Oct 28, 2025
801ccb2
Added tewst suite name for only run
can-angun Oct 28, 2025
5b8b33d
Fixed linter errors
can-angun Oct 28, 2025
45053c7
Fix linter errors
can-angun Oct 28, 2025
9eabfc2
Removed cypress eslint parameters
can-angun Oct 28, 2025
a4d36f7
Fix linter errors
can-angun Oct 28, 2025
9d20646
Added pdfjs plugin
can-angun Oct 28, 2025
2e39e93
Fixed missing dependencies fails
can-angun Oct 28, 2025
02cd905
Fixed dep fails
can-angun Oct 28, 2025
03850b4
Fixed cypress config errors
can-angun Oct 28, 2025
7889c6a
Fixed pdfParse is not a function error
can-angun Oct 28, 2025
2f94706
Merge branch 'master' into QT-344
ar2rsawseen Oct 30, 2025
96dd896
Merge branch 'master' into QT-344
can-angun Nov 3, 2025
7c1dac2
Fixed missing dependencies fails
can-angun Nov 3, 2025
9c63d70
Fxed linter errors
can-angun Nov 3, 2025
18ed7a6
Logo added
can-angun Nov 3, 2025
c9c91b8
Fixed logo path
can-angun Nov 3, 2025
de2e7a4
Fixed logo path
can-angun Nov 3, 2025
6cd94d0
Fixed countly logo path
can-angun Nov 3, 2025
06601fb
Added 2mins timeout
can-angun Nov 3, 2025
bfa3275
fixed countly logo path
can-angun Nov 3, 2025
b19aabf
Fixed logo path
can-angun Nov 3, 2025
cb5731b
Added downloads upload
can-angun Nov 3, 2025
2a156bc
Fixed downloaded pdf path
can-angun Nov 3, 2025
fe513d0
Edited pdf path for test
can-angun Nov 3, 2025
bab52fb
Fixed path
can-angun Nov 3, 2025
6d81950
Removed expects for test
can-angun Nov 3, 2025
57b1685
Added hasImage logoFound expect
can-angun Nov 3, 2025
bc7f538
Added image verify
can-angun Nov 3, 2025
8b8220b
Added first text verify
can-angun Nov 3, 2025
9ca77c6
Added text verfiy
can-angun Nov 3, 2025
26c1726
Updated text
can-angun Nov 3, 2025
c4947fa
Opened all tests
can-angun Nov 3, 2025
9427848
Empty-Commit
can-angun Nov 4, 2025
0ccd622
Updated install chrome and dependencies scripts
can-angun Nov 4, 2025
bce02e0
Updated install chrome script
can-angun Nov 4, 2025
4d63099
Updated install scripts
can-angun Nov 4, 2025
ca0a943
Fixed install chrome script
can-angun Nov 4, 2025
1584947
Empty-Commit
can-angun Nov 4, 2025
053b9fe
Updated cases it desc
can-angun Nov 4, 2025
aae1517
Merge branch 'master' into QT-344
can-angun Nov 4, 2025
e2372a9
Updated job name desc
can-angun Nov 4, 2025
ca7ec47
Merge branch 'QT-344' of github.com:Countly/countly-server into QT-344
can-angun Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,13 @@ jobs:
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/chrome.deb
apt install -y /tmp/chrome.deb

- name: Install Sharp dependencies for image processing
shell: bash
run: |
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y libvips-dev

- name: Copy code
shell: bash
run: cp -rf ./* /opt/countly
Expand Down Expand Up @@ -331,6 +338,6 @@ jobs:
working-directory: /opt/countly/ui-tests/cypress
run: |
ARTIFACT_ARCHIVE_NAME="$(date '+%Y%m%d-%H.%M')_${GITHUB_REPOSITORY#*/}_CI#${{ github.run_number }}_${{ matrix.test_type }}.tar.gz"
mkdir -p screenshots videos
tar zcvf "$ARTIFACT_ARCHIVE_NAME" screenshots videos
mkdir -p screenshots videos downloads
tar zcvf "$ARTIFACT_ARCHIVE_NAME" screenshots videos downloads
curl -o /tmp/uploader.log -u "${{ secrets.BOX_UPLOAD_AUTH }}" ${{ secrets.BOX_UPLOAD_PATH }} -T "$ARTIFACT_ARCHIVE_NAME"
130 changes: 116 additions & 14 deletions ui-tests/cypress.config.sample.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
const { defineConfig } = require("cypress");
const fs = require('fs');
const fs = require("fs");
const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.js");
const { PNG } = require("pngjs");
const sharp = require("sharp");

// Define missing DOMMatrix in Node context (for pdfjs)
if (typeof global.DOMMatrix === "undefined") {
global.DOMMatrix = class DOMMatrix { };
}

module.exports = defineConfig({
e2e: {
Expand All @@ -14,20 +22,116 @@ module.exports = defineConfig({
watchForFileChanges: true,
video: true,
setupNodeEvents(on, config) {
on('after:spec', (spec, results) => {
if (results && results.video) {
const failures = results.tests.some((test) =>
test.attempts.some((attempt) => attempt.state === 'failed')
);
if (!failures) {
// delete the video if the spec passed and no tests retried
const videoPath = results.video;
if (fs.existsSync(videoPath)) {
fs.unlinkSync(videoPath);
// Task: verify PDF images, logo, and text content
on("task", {
async verifyPdf({ filePath, options = {} }) {
// options: { referenceLogoPath: string }

// Load PDF file
const data = new Uint8Array(fs.readFileSync(filePath));
const pdfDoc = await pdfjsLib.getDocument({ data }).promise;

// Import pixelmatch only if logo check is needed
let pixelmatch;
const doLogoCheck = !!options.referenceLogoPath;
if (doLogoCheck) {
const pm = await import("pixelmatch");
pixelmatch = pm.default;
}

let hasImage = false;
let logoFound = false;
let extractedText = ""; //store text here

// Loop through all pages
for (let p = 1; p <= pdfDoc.numPages; p++) {
const page = await pdfDoc.getPage(p);

//Extract text content from page
const textContent = await page.getTextContent();
const pageText = textContent.items.map((item) => item.str).join(" ");
extractedText += pageText + "\n";

//Check for image operators
const ops = await page.getOperatorList();

for (let i = 0; i < ops.fnArray.length; i++) {
const fn = ops.fnArray[i];
const args = ops.argsArray[i];

if (
fn === pdfjsLib.OPS.paintImageXObject ||
fn === pdfjsLib.OPS.paintJpegXObject ||
fn === pdfjsLib.OPS.paintInlineImageXObject
) {
hasImage = true;

if (doLogoCheck && args[0]) {
const objName = args[0];
const imgData = await page.objs.get(objName);
if (!imgData) {
continue;
}

const pdfImg = new PNG({ width: imgData.width, height: imgData.height });
pdfImg.data = imgData.data;

const pdfBuffer = PNG.sync.write(pdfImg);
const refLogo = PNG.sync.read(fs.readFileSync(options.referenceLogoPath));

const resizedPdfBuffer = await sharp(pdfBuffer)
.resize(refLogo.width, refLogo.height)
.png()
.toBuffer();

const resizedPdfImg = PNG.sync.read(resizedPdfBuffer);

const diff = new PNG({ width: refLogo.width, height: refLogo.height });
const mismatched = pixelmatch(
refLogo.data,
resizedPdfImg.data,
diff.data,
refLogo.width,
refLogo.height,
{ threshold: 0.1 }
);

if (mismatched === 0) {
logoFound = true;
break;
}
}
}
}

if ((doLogoCheck && logoFound) || (!doLogoCheck && hasImage)) {
break;
}
}

if (doLogoCheck && !logoFound) {
throw new Error("Logo in PDF does not match reference image");
}

//Return with extracted text
return {
hasImage,
logoFound,
text: extractedText,
numPages: pdfDoc.numPages
};
},
});

on("after:spec", (spec, results) => {
if (results?.video) {
const hasFailures = results.tests.some((t) => t.attempts.some((a) => a.state === "failed"));
if (!hasFailures && fs.existsSync(results.video)) {
fs.unlinkSync(results.video);
}
}
});

on("before:browser:launch", (browser, launchOptions) => {
if (["chrome", "edge", "electron"].includes(browser.name)) {
if (browser.isHeadless) {
Expand All @@ -42,6 +146,4 @@ module.exports = defineConfig({
});
},
},
});


});
53 changes: 52 additions & 1 deletion ui-tests/cypress/e2e/dashboard/dashboards/dashboards.cy.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/* eslint-env mocha */
/* global cy, Cypress, expect */

import user from '../../../fixtures/user.json';
const getApiKey = require('../../../api/getApiKey');
const navigationHelpers = require('../../../support/navigations');
const helper = require('../../../support/helper');
const loginHelpers = require('../../../lib/login/login');
Expand All @@ -17,7 +21,7 @@ describe('Create New Custom Dashboard', () => {
});

it(`
Create a custom dashboard with a widget and an email report, and then verify the report preview using these parameters:
Create a custom dashboard with a widget and an email report, verify the report preview, and validate the downloaded PDF content:
//***Dashboard***
Dashboard Visibility: All Users (default)
//***Widget***
Expand Down Expand Up @@ -84,6 +88,53 @@ describe('Create New Custom Dashboard', () => {

reportHelper.openReportPreviewButton();
reportHelper.verifyReportPreviewPageImage();

// Get the current URL
cy.url().then((currentUrl) => {

//Get API key
getApiKey.request(user.username, user.password).then((apiKey) => {

//Change preview to PDF and add api_key parameter
const urlObj = new URL(currentUrl);
urlObj.pathname = urlObj.pathname.replace('preview', 'pdf');
urlObj.searchParams.set('api_key', apiKey);
const pdfURL = urlObj.toString();

cy.log('Generated PDF URL:', pdfURL);

//Download the PDF and verify its content
cy.request({
url: pdfURL,
encoding: 'binary',
timeout: 120000,
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.headers['content-type']).to.include('application/pdf');

const buf = Buffer.from(response.body, 'binary');
expect(buf.slice(0, 4).toString()).to.eq('%PDF');
expect(buf.length).to.be.greaterThan(50000); // More than 50KB to ensure it's not empty

// Save the PDF to disk
cy.writeFile('cypress/downloads/generated-report.pdf', buf);
});
});
});

// Verify PDF content
cy.task("verifyPdf", {
filePath: "cypress/downloads/generated-report.pdf",
options: {
referenceLogoPath: "cypress/fixtures/testFiles/countly-logo.png",
checkText: true
}
}).then((result) => {
expect(result.logoFound).to.be.true;
expect(result.hasImage).to.be.true;
expect(result.text).to.include("Sent by Countly | Unsubscribe");
expect(result.text).to.include("Report settings | Get help");
});
});

it(`Create a private custom dashboard and duplicate it and edit it and delete it then verify the flow`, function() {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading