Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
973d2fd
WIP #280 - Move asset files and reports into a folder
craigfowler Feb 26, 2026
01935e4
#208 change test
craigfowler Feb 26, 2026
73610cb
#208 - fix test
craigfowler Feb 26, 2026
6f46d71
#280 - fix code quality issue
craigfowler Feb 27, 2026
b2d8f2b
Trivial - improve config readability
craigfowler Feb 27, 2026
55e731d
WIP #280 - .NET embedding of assets into reports
craigfowler Feb 27, 2026
100956b
Add content type info: #280
craigfowler Feb 28, 2026
dc50c5d
WIP #280 - Update ID of loading mask element
craigfowler Mar 1, 2026
4f95da5
WIP #280 - Lost track of what's done here
craigfowler Mar 7, 2026
308e60a
WIP #280 - Fix case sensitivity issue
craigfowler Mar 7, 2026
c28b86a
Switch back to original package ref, and add diagnostic step
craigfowler Mar 7, 2026
95f2659
Attempt to fix build
craigfowler Mar 7, 2026
43c6b73
Another attempt to fix the build
craigfowler Mar 7, 2026
da66c1e
WIP #280 - Revert attempts to fix MimeTypes package
craigfowler Mar 7, 2026
3adff6f
Include MimeTypes via manual file copy
craigfowler Mar 7, 2026
8278a98
Fix minor tech debt issues
craigfowler Mar 7, 2026
e51bd73
WIP #208 - Add some test coverage
craigfowler Mar 7, 2026
185b0d4
Fix ignored files #208
craigfowler Mar 7, 2026
d4aaf04
Fix tests
craigfowler Mar 7, 2026
8ef17bd
Add LFS checkout
craigfowler Mar 7, 2026
2bc60c9
Complete Litebox functionality #280
craigfowler Mar 7, 2026
30dfee7
WIP #280 - Minor tech improvements
craigfowler Mar 7, 2026
5f14974
Fix failing test
craigfowler Mar 7, 2026
de54196
Provisionally resolve #280 - Add test coverage
craigfowler Mar 7, 2026
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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
4 changes: 2 additions & 2 deletions .github/workflows/crossBrowserTesting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Screenplay JSON reports ${{ matrix.browserName }}_${{ matrix.browserVersion }}_${{ matrix.os }}_${{ matrix.osVersion }}
path: Tests/CSF.Screenplay.Selenium.Tests/**/ScreenplayReport_*.json
path: Tests/CSF.Screenplay.Selenium.Tests/**/ScreenplayReport.json
- name: Convert Screenplay reports to HTML
continue-on-error: true
run: |
for report in $(find Tests/CSF.Screenplay.Selenium.Tests/ -type f -name "ScreenplayReport_*.json")
for report in $(find Tests/CSF.Screenplay.Selenium.Tests/ -type f -name "ScreenplayReport.json")
do
reportDir=$(dirname "$report")
outputFile="$reportDir/ScreenplayReport.html"
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/dotnetCi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true

# Install build dependencies

Expand Down Expand Up @@ -165,11 +166,11 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: Screenplay JSON reports
path: Tests/**/ScreenplayReport_*.json
path: Tests/**/ScreenplayReport.json
- name: Convert Screenplay reports to HTML
continue-on-error: true
run: |
for report in $(find Tests/ -type f -name "ScreenplayReport_*.json")
for report in $(find Tests/ -type f -name "ScreenplayReport.json")
do
reportDir=$(dirname "$report")
outputFile="$reportDir/ScreenplayReport.html"
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ node_modules/
*.orig
/CSF.Screenplay.JsonToHtmlReport/template/
/CSF.Screenplay.JsonToHtmlReport.Template/src/output/
**/ScreenplayReport*
**/ScreenplayReport_*/**/*
**/ScreenplayReport.html
**/TestResult.xml
.obsidian/
packages/
2 changes: 1 addition & 1 deletion CSF.Screenplay.Abstractions/Reporting/IGetsReportPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace CSF.Screenplay.Reporting
public interface IGetsReportPath
{
/// <summary>
/// Gets the path to which the report should be written.
/// Gets the directory path to which the report files should be written.
/// </summary>
/// <remarks>
/// <para>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.IO;

namespace CSF.Screenplay.Reporting
{
/// <summary>
/// Extension methods for <see cref="IGetsReportPath"/>.
/// </summary>
public static class ReportPathProviderExtensions
{
const string reportFilename = "ScreenplayReport.json";

/// <summary>
/// Gets the file path to which the report JSON file should be written.
/// </summary>
/// <remarks>
/// <para>
/// If the returned path is <see langword="null" /> then Screenplay's reporting functionality should be disabled and no report should be written.
/// Otherwise, implementations of this interface should return an absolute file system path to which the report JSON file should be written.
/// This path must be writable by the executing process.
/// </para>
/// <para>
/// Reporting could be disabled if either the Screenplay Options report path is <see langword="null" /> or a whitespace-only string, or if the path
/// indicated by those options is not writable.
/// </para>
/// </remarks>
/// <returns>The report file path.</returns>
public static string GetReportFilePath(this IGetsReportPath provider)
{
var directoryPath = provider.GetReportPath();
if(directoryPath is null) return null;
return Path.Combine(directoryPath, reportFilename);
}
}
}
71 changes: 71 additions & 0 deletions CSF.Screenplay.JsonToHtmlReport.Template/src/css/litebox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@layer layout {
#liteboxMask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000044;
backdrop-filter: blur(3px);
display: none;
z-index: 100;
}
#liteboxMask.open {
display: block;
}
#litebox {
position: fixed;
top: 2em;
left: 2em;
right: 2em;
bottom: 2em;
background: #F0F0F0;
border-radius: 2em;
border: 1px solid #999;
box-shadow: 3px 3px 5px 2px #00000099;
padding: 1em;
display: flex;
flex-direction: column;
}
#litebox .controls {
display: flex;
height: 24px;
flex-direction: row;
align-items: start;
justify-content: end;
flex: 0 0 auto;
}
#litebox .controls button {
border: 0 none;
display: inline-block;
padding: 12px;
background: transparent center / contain no-repeat;
cursor: pointer;
margin-left: 12px;
}
#litebox .controls button:focus {
outline: 1px dotted #999;
}
#litebox .controls button span {
display: none;
}
#litebox .controls .download {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktY2xvdWQtYXJyb3ctZG93bi1maWxsIiB2aWV3Qm94PSIwIDAgMTYgMTYiPgogIDxwYXRoIGQ9Ik04IDJhNS41MyA1LjUzIDAgMCAwLTMuNTk0IDEuMzQyYy0uNzY2LjY2LTEuMzIxIDEuNTItMS40NjQgMi4zODNDMS4yNjYgNi4wOTUgMCA3LjU1NSAwIDkuMzE4IDAgMTEuMzY2IDEuNzA4IDEzIDMuNzgxIDEzaDguOTA2QzE0LjUwMiAxMyAxNiAxMS41NyAxNiA5Ljc3M2MwLTEuNjM2LTEuMjQyLTIuOTY5LTIuODM0LTMuMTk0QzEyLjkyMyAzLjk5OSAxMC42OSAyIDggMm0yLjM1NCA2Ljg1NC0yIDJhLjUuNSAwIDAgMS0uNzA4IDBsLTItMmEuNS41IDAgMSAxIC43MDgtLjcwOEw3LjUgOS4yOTNWNS41YS41LjUgMCAwIDEgMSAwdjMuNzkzbDEuMTQ2LTEuMTQ3YS41LjUgMCAwIDEgLjcwOC43MDgiLz4KPC9zdmc+);
}
#litebox .controls .close {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmkteC1jaXJjbGUiIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTggMTVBNyA3IDAgMSAxIDggMWE3IDcgMCAwIDEgMCAxNG0wIDFBOCA4IDAgMSAwIDggMGE4IDggMCAwIDAgMCAxNiIvPgogIDxwYXRoIGQ9Ik00LjY0NiA0LjY0NmEuNS41IDAgMCAxIC43MDggMEw4IDcuMjkzbDIuNjQ2LTIuNjQ3YS41LjUgMCAwIDEgLjcwOC43MDhMOC43MDcgOGwyLjY0NyAyLjY0NmEuNS41IDAgMCAxLS43MDguNzA4TDggOC43MDdsLTIuNjQ2IDIuNjQ3YS41LjUgMCAwIDEtLjcwOC0uNzA4TDcuMjkzIDggNC42NDYgNS4zNTRhLjUuNSAwIDAgMSAwLS43MDgiLz4KPC9zdmc+);
}
#litebox .content {
margin: 12px 0;
flex: 1 1 0;
object-fit: contain;
min-width: 0;
min-height: 0;
max-width: 100%;
max-height: 100%;
}
#litebox .summary {
flex: 0 0 auto;
text-align: center;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@layer spinner {
#pageMask {
#loadingMask {
position: fixed;
top: 0;
left: 0;
Expand Down
5 changes: 4 additions & 1 deletion CSF.Screenplay.JsonToHtmlReport.Template/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import "./css/layout.css";
import "./css/content.css";
import "./css/summaryTable.css";
import "./css/scenarioList.css";
import "./css/litebox.css";
import { getReportLoader } from "./js/ReportLoader";
import { activatePage } from "./js/activatePage";
import { updateReportTime } from "./js/updateReportTime";
import { getScenarioAggregator } from "./js/ScenarioAggregator";
import { getSummaryGenerator } from "./js/SummaryGenerator";
import { getReportWriter } from "./js/ReportWriter";
import { Litebox } from "./js/Litebox";

document.onreadystatechange = () => {
if (document.readyState !== "complete") return;

const litebox = new Litebox();
const loader = getReportLoader();
const report = loader.loadJson();

Expand All @@ -26,7 +29,7 @@ document.onreadystatechange = () => {
const summaryGenerator = getSummaryGenerator(scenariosByFeature);
const summary = summaryGenerator.generateSummary();
const reportWriter = getReportWriter();
const featureReport = reportWriter.getReport(scenariosByFeature);
const featureReport = reportWriter.getReport(scenariosByFeature, litebox);

activatePage(summary, featureReport);
}
Expand Down
57 changes: 57 additions & 0 deletions CSF.Screenplay.JsonToHtmlReport.Template/src/js/Litebox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getElementById } from './getElementById';

const
maskId = 'liteboxMask',
downloadSelector = '#litebox .download',
closeSelector = '#litebox .close',
contentSelector = '#litebox .content',
summarySelector = '#litebox .summary';

export class Litebox {
#main;
#download;
#close;
#content;
#summary;
#imageUrl;
#isBlobUrl;
#fileName;
#downloadHandler;

open(imageUrl, filename, summary, isBlobUrl) {
this.#imageUrl = imageUrl;
this.#isBlobUrl = isBlobUrl;
this.#main.classList.add('open');

this.#fileName = filename;
this.#summary.textContent = summary;
this.#content.src = this.#imageUrl;
this.#content.title = filename;
this.#downloadHandler = () => this.#onDownloadClick();
this.#download.addEventListener('click', this.#downloadHandler);
}

close() {
this.#main.classList.remove('open');
if(this.#isBlobUrl) {
URL.revokeObjectURL(this.#imageUrl);
}
this.#download.removeEventListener('click', this.#downloadHandler);
}

#onDownloadClick() {
const hyperLink = document.createElement('a');
hyperLink.href = this.#imageUrl;
hyperLink.download = this.#fileName;
hyperLink.click();
}

constructor() {
this.#main = getElementById(maskId);
this.#download = document.querySelector(downloadSelector);
this.#close = document.querySelector(closeSelector);
this.#close.addEventListener('click', () => this.close());
this.#content = document.querySelector(contentSelector);
this.#summary = document.querySelector(summarySelector);
}
}
61 changes: 61 additions & 0 deletions CSF.Screenplay.JsonToHtmlReport.Template/src/js/Litebox.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @jest-environment jsdom
*/

import { Litebox } from "./Litebox";

test('open should show the mask', () => {
document.body.innerHTML = htmlContent;
const sut = new Litebox();

sut.open('foo/bar.png', 'bar.png', 'test image', false);

const hasOpenClass = document.querySelector('#liteboxMask').classList.contains('open');
expect(hasOpenClass).toBe(true);
});

test('open should set the summary using the image summary', () => {
document.body.innerHTML = htmlContent;
const sut = new Litebox();

sut.open('foo/bar.png', 'bar.png', 'test image', false);

const summaryText = document.querySelector('#liteboxMask .summary').textContent;
expect(summaryText).toBe('test image');
});

test('open should set the image src and title', () => {
document.body.innerHTML = htmlContent;
const sut = new Litebox();

sut.open('foo/bar.png', 'bar.png', 'test image', false);

const src = document.querySelector('#liteboxMask .content').src;
expect(src).toMatch(/foo\/bar.png$/);

const title = document.querySelector('#liteboxMask .content').title;
expect(title).toBe('bar.png');
});

test('close should remove the open class', () => {
document.body.innerHTML = htmlContent;
document.querySelector('#liteboxMask').classList.add('open');
const sut = new Litebox();

sut.open('foo/bar.png', 'bar.png', 'test image', false);
sut.close();

const hasOpenClass = document.querySelector('#liteboxMask').classList.contains('open');
expect(hasOpenClass).toBe(false);
});

const htmlContent = `<div id="liteboxMask">
<div id="litebox">
<fieldset class="controls">
<button class="download" title="Download"><span>Download</span></button>
<button class="close" title="Close"><span>Close</span></button>
</fieldset>
<img alt="" class="content">
<p class="summary">Asset summary</p>
</div>
</div>`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { imageTypes } from "./ImageTypes";

export class AssetBehaviour {
#litebox;
#linkElement;
#assetModel;

initialise() {
this.#linkElement.textContent = this.#assetModel.FileSummary;

if(imageTypes.includes(this.#assetModel.ContentType))
this.#initialiseImage();
else
this.#initialiseDownload();
}

#initialiseImage() {
this.#linkElement.addEventListener('click', ev => {
const usesBlobUrl = !!this.#assetModel.FileData
const url = usesBlobUrl ? this.#getBlobUrl() : this.#assetModel.FilePath;
this.#litebox.open(url, this.#assetModel.FileName, this.#assetModel.FileSummary, usesBlobUrl);
ev.preventDefault();
});
this.#linkElement.href = null;
}

#initialiseDownload() {
const usesBlobUrl = !!this.#assetModel.FileData
const url = usesBlobUrl ? this.#getBlobUrl() : this.#assetModel.FilePath;
this.#linkElement.href = url;
this.#linkElement.download = this.#assetModel.FileName;
}

#getBlobUrl() {
const dataBytes = this.#base64ToBytes(this.#assetModel.FileData);
const blob = new Blob([dataBytes], { type: this.#assetModel.ContentType });
return URL.createObjectURL(blob);
}

#base64ToBytes(base64Data) {
const binaryData = atob(base64Data);
const buffer = new Uint8Array(binaryData.length);
for (let i = 0; i < binaryData.length; i++) {
buffer[i] = binaryData.codePointAt(i);
}
return buffer;
}

constructor(litebox, element, assetModel) {
this.#litebox = litebox;
this.#linkElement = element.querySelector('a')
this.#assetModel = assetModel;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* A list of image MIME types which browsers can display natively.
*/
export const imageTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/avif',
'image/bmp',
'image/x-icon',
'image/vnd.microsoft.icon'
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AssetBehaviour } from "./AssetBehaviour";
export { imageTypes } from "./ImageTypes";
Loading
Loading