diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3201cb851..74ca8ce23 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -71,7 +71,12 @@ jobs:
cache: maven
- name: Build and run tests
- run: ./mvnw -B -ntp clean verify
+ # Reactor slice: the engine plus the extracted graph-compose-testing
+ # module and the graph-compose-qa cross-module suites (which need the
+ # testing module + the engine test-jar on the classpath and so cannot
+ # live in the engine's own test scope). -am pulls the fonts / emoji
+ # upstreams; render-docx / bundle / examples / benchmarks are excluded.
+ run: ./mvnw -B -ntp -f aggregator/pom.xml clean verify -pl :graph-compose,:graph-compose-testing,:graph-compose-qa -am
- name: Generate Javadoc
# Run only on the baseline JDK — Javadoc output is identical
@@ -120,6 +125,13 @@ jobs:
# dependency) so the examples build resolves it.
run: ./mvnw -B -ntp -f render-docx/pom.xml -DskipTests install
+ - name: Install graph-compose-testing (consumed by the examples module)
+ # LayoutSnapshotRegressionExample uses LayoutSnapshotJson and EngineDeckData
+ # reads benchmark JSON through the jackson it brings; the testing module is
+ # not on Central, so install it after the engine (its graph-compose
+ # dependency) before the examples build resolves it.
+ run: ./mvnw -B -ntp -f testing/pom.xml -DskipTests install
+
- name: Compile examples module
run: ./mvnw -B -ntp -f examples/pom.xml clean compile
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index f0be42a20..69f6974f0 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -109,6 +109,16 @@ jobs:
CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+ - name: Publish testing to Maven Central
+ # Consumer testing support (LayoutSnapshotAssertions / PdfVisualRegression),
+ # lockstep-versioned with the engine (ships on the same v* tag). graph-compose
+ # resolves from the local repo installed by the engine deploy above.
+ run: ./mvnw -B -ntp -f testing/pom.xml -P release -DskipTests -Dgpg.skip=false deploy
+ env:
+ CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
+ CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+
- name: Publish bundle to Maven Central
# The graph-compose-bundle convenience aggregate pins this engine
# version + a compatible graph-compose-fonts version. It tracks the
diff --git a/.gitignore b/.gitignore
index 57ac55088..17c75d506 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ build/
.DS_Store
/logs/
benchmarks/logs/
+qa/logs/
/CV_Generated.pdf
*.pdf
# Allow PDF previews that are committed README assets.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e94fac197..f3a72e20b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,6 +46,13 @@ for this cycle.
to `com.demcha.compose.document.backend.semantic.pptx`; add
`graph-compose-render-docx` (it brings POI transitively) to export DOCX. The
`no-poi` build profile is retired.
+- `graph-compose-testing` is now a separate artifact: the consumer testing
+ support — `LayoutSnapshotAssertions` (deterministic layout snapshots) and
+ `PdfVisualRegression` (pixel-diff of rendered pages) — leaves the
+ `graph-compose` jar together with its Jackson and PDFBox dependencies. The
+ `com.demcha.compose.testing.layout` / `com.demcha.compose.testing.visual`
+ packages are unchanged, so imports stay the same; add `graph-compose-testing`
+ at test scope to keep using them.
## v1.9.0 — 2026-06-29
diff --git a/aggregator/pom.xml b/aggregator/pom.xml
index 2f5361242..e1b7814bd 100644
--- a/aggregator/pom.xml
+++ b/aggregator/pom.xml
@@ -61,8 +61,10 @@
../fonts
../emoji
../render-docx
+ ../testing
../bundle
../examples
../benchmarks
+ ../qa
diff --git a/docs/operations/layout-snapshot-testing.md b/docs/operations/layout-snapshot-testing.md
index 16e8cd46d..849253271 100644
--- a/docs/operations/layout-snapshot-testing.md
+++ b/docs/operations/layout-snapshot-testing.md
@@ -288,7 +288,7 @@ Or update a focused test only:
./mvnw "-Dgraphcompose.updateSnapshots=true" "-Dtest=MyFeatureLayoutSnapshotTest" test
```
-The same property works for downstream projects that use `LayoutSnapshotAssertions` from the published GraphCompose artifact.
+The same property works for downstream projects that use `LayoutSnapshotAssertions` from the published `graph-compose-testing` artifact.
In normal mode:
diff --git a/docs/operations/visual-regression-testing.md b/docs/operations/visual-regression-testing.md
index 980887cda..9417e943a 100644
--- a/docs/operations/visual-regression-testing.md
+++ b/docs/operations/visual-regression-testing.md
@@ -23,7 +23,7 @@ The semantic layer is cheap, deterministic, and cross-platform stable. The pixel
## Public API
-The harness is `com.demcha.compose.testing.visual.PdfVisualRegression` (`@since 1.6.9`), a sibling to the semantic `com.demcha.compose.testing.layout.*` helpers. It ships in the main artifact, so library consumers use the exact helpers GraphCompose uses in its own tests.
+The harness is `com.demcha.compose.testing.visual.PdfVisualRegression` (`@since 1.6.9`), a sibling to the semantic `com.demcha.compose.testing.layout.*` helpers. It ships in the `graph-compose-testing` artifact (split out of the engine jar in 2.0) — add it at test scope, and consumers use the exact helpers GraphCompose uses in its own tests.
### Assert a committed baseline
diff --git a/docs/recipes/snapshot-testing.md b/docs/recipes/snapshot-testing.md
index 41098784d..2712b81b5 100644
--- a/docs/recipes/snapshot-testing.md
+++ b/docs/recipes/snapshot-testing.md
@@ -29,8 +29,10 @@ void invoiceLayoutIsStable() throws Exception {
The slash-delimited key is a logical path: this example compares against
`src/test/resources/layout-snapshots/templates/invoice/invoice_baseline.json`.
-`LayoutSnapshotAssertions` ships in the main GraphCompose artifact, so
-consumer projects can use it without any extra test dependency.
+`LayoutSnapshotAssertions` ships in the `graph-compose-testing` artifact
+(`io.github.demchaav:graph-compose-testing`), split out of the engine jar in
+2.0. Add it at test scope alongside `graph-compose`; the import path is
+unchanged.
## First run and updates
diff --git a/examples/pom.xml b/examples/pom.xml
index c96aad7fe..f135206b7 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -76,6 +76,17 @@
${graphcompose.version}
+
+
+ io.github.demchaav
+ graph-compose-testing
+ ${graphcompose.version}
+
+
ch.qos.logback
logback-classic
diff --git a/pom.xml b/pom.xml
index 9354ac2c1..7156bc88f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,7 +47,6 @@
0.64.8
- 2.21.4
1.5.37
1.18.46
3.0.7
@@ -103,13 +102,6 @@
pom
import
-
- com.fasterxml.jackson
- jackson-bom
- ${jackson.bom.version}
- pom
- import
-
@@ -181,21 +173,6 @@
${flexmark.version}
-
- com.fasterxml.jackson.core
- jackson-annotations
-
-
-
- com.fasterxml.jackson.core
- jackson-core
-
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
com.google.zxing
core
@@ -461,7 +438,7 @@
true
public
true
- com.demcha.compose.document:com.demcha.compose.testing
+ com.demcha.compose.document
diff --git a/qa/pom.xml b/qa/pom.xml
new file mode 100644
index 000000000..40f808c24
--- /dev/null
+++ b/qa/pom.xml
@@ -0,0 +1,154 @@
+
+
+ 4.0.0
+
+
+
+ io.github.demchaav
+ graph-compose-build
+ 2.0.0-SNAPSHOT
+ ../aggregator/pom.xml
+
+
+ graph-compose-qa
+ GraphCompose QA
+ Non-published cross-module test suites and shared fixtures for GraphCompose.
+
+
+ UTF-8
+ 17
+
+ 3.15.0
+ 3.5.6
+
+ 2.21.4
+ 6.1.1
+ 3.27.7
+ 5.23.0
+ 1.5.37
+ 3.0.7
+
+
+
+
+
+ io.github.demchaav
+ graph-compose
+ ${project.version}
+ test
+
+
+
+ io.github.demchaav
+ graph-compose
+ ${project.version}
+ test-jar
+ tests
+ test
+
+
+ io.github.demchaav
+ graph-compose-testing
+ ${project.version}
+ test
+
+
+ io.github.demchaav
+ graph-compose-fonts
+ ${graphcompose.fonts.version}
+ test
+
+
+ io.github.demchaav
+ graph-compose-emoji
+ ${graphcompose.emoji.version}
+ test
+
+
+
+ org.apache.pdfbox
+ pdfbox
+ ${pdfbox.version}
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.bom.version}
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.bom.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.plugin.version}
+
+ ${maven.compiler.release}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.surefire.plugin.version}
+
+
+
+
diff --git a/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java b/qa/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java
rename to qa/src/test/java/com/demcha/compose/document/api/DocumentListNodeTest.java
diff --git a/src/test/java/com/demcha/compose/document/api/DocumentSessionImageTest.java b/qa/src/test/java/com/demcha/compose/document/api/DocumentSessionImageTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/api/DocumentSessionImageTest.java
rename to qa/src/test/java/com/demcha/compose/document/api/DocumentSessionImageTest.java
diff --git a/src/test/java/com/demcha/compose/document/api/PathNodeRenderingTest.java b/qa/src/test/java/com/demcha/compose/document/api/PathNodeRenderingTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/api/PathNodeRenderingTest.java
rename to qa/src/test/java/com/demcha/compose/document/api/PathNodeRenderingTest.java
diff --git a/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java
rename to qa/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java
diff --git a/src/test/java/com/demcha/compose/document/dsl/CanvasLayerBuilderTest.java b/qa/src/test/java/com/demcha/compose/document/dsl/CanvasLayerBuilderTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/dsl/CanvasLayerBuilderTest.java
rename to qa/src/test/java/com/demcha/compose/document/dsl/CanvasLayerBuilderTest.java
diff --git a/src/test/java/com/demcha/compose/document/dsl/ChartLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/dsl/ChartLayoutSnapshotTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/dsl/ChartLayoutSnapshotTest.java
rename to qa/src/test/java/com/demcha/compose/document/dsl/ChartLayoutSnapshotTest.java
diff --git a/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java b/qa/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java
rename to qa/src/test/java/com/demcha/compose/document/dsl/ListBuilderNestedTest.java
diff --git a/src/test/java/com/demcha/compose/document/dsl/ShapeContainerLayoutSnapshotTest.java b/qa/src/test/java/com/demcha/compose/document/dsl/ShapeContainerLayoutSnapshotTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/dsl/ShapeContainerLayoutSnapshotTest.java
rename to qa/src/test/java/com/demcha/compose/document/dsl/ShapeContainerLayoutSnapshotTest.java
diff --git a/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java b/qa/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java
rename to qa/src/test/java/com/demcha/compose/document/table/TableCellComposedContentTest.java
diff --git a/src/test/java/com/demcha/compose/document/templates/TemplateTestSupport.java b/qa/src/test/java/com/demcha/compose/document/templates/TemplateTestSupport.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/templates/TemplateTestSupport.java
rename to qa/src/test/java/com/demcha/compose/document/templates/TemplateTestSupport.java
diff --git a/src/test/java/com/demcha/compose/document/templates/coverletter/presets/CoverLetterV2VisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/coverletter/presets/CoverLetterV2VisualParityTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/templates/coverletter/presets/CoverLetterV2VisualParityTest.java
rename to qa/src/test/java/com/demcha/compose/document/templates/coverletter/presets/CoverLetterV2VisualParityTest.java
diff --git a/src/test/java/com/demcha/compose/document/templates/cv/presets/CvV2VisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvV2VisualParityTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/templates/cv/presets/CvV2VisualParityTest.java
rename to qa/src/test/java/com/demcha/compose/document/templates/cv/presets/CvV2VisualParityTest.java
diff --git a/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java
rename to qa/src/test/java/com/demcha/compose/document/templates/invoice/presets/InvoiceV2VisualParityTest.java
diff --git a/src/test/java/com/demcha/compose/document/templates/proposal/presets/ProposalV2VisualParityTest.java b/qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/ProposalV2VisualParityTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/document/templates/proposal/presets/ProposalV2VisualParityTest.java
rename to qa/src/test/java/com/demcha/compose/document/templates/proposal/presets/ProposalV2VisualParityTest.java
diff --git a/src/test/java/com/demcha/compose/engine/integration/FeatureShowcaseRenderTest.java b/qa/src/test/java/com/demcha/compose/engine/integration/FeatureShowcaseRenderTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/engine/integration/FeatureShowcaseRenderTest.java
rename to qa/src/test/java/com/demcha/compose/engine/integration/FeatureShowcaseRenderTest.java
diff --git a/src/test/java/com/demcha/compose/engine/integration/RepositoryShowcaseRenderTest.java b/qa/src/test/java/com/demcha/compose/engine/integration/RepositoryShowcaseRenderTest.java
similarity index 94%
rename from src/test/java/com/demcha/compose/engine/integration/RepositoryShowcaseRenderTest.java
rename to qa/src/test/java/com/demcha/compose/engine/integration/RepositoryShowcaseRenderTest.java
index 3543642ab..7c9b5df20 100644
--- a/src/test/java/com/demcha/compose/engine/integration/RepositoryShowcaseRenderTest.java
+++ b/qa/src/test/java/com/demcha/compose/engine/integration/RepositoryShowcaseRenderTest.java
@@ -24,13 +24,31 @@
import org.junit.jupiter.api.Test;
import java.awt.Color;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class RepositoryShowcaseRenderTest {
- private static final Path COVER_ASSET = Path.of("assets", "GraphComposeCover.png");
+ private static final Path COVER_ASSET = locateRepoAsset(Path.of("assets", "GraphComposeCover.png"));
+
+ /**
+ * Resolves a repository-root asset independently of which module runs the
+ * test. The suite moved from the engine module (working dir = repo root) to
+ * the qa reactor tail (working dir = {@code qa/}); walking up from the
+ * working directory until the relative path resolves keeps the cover image
+ * reachable from either location.
+ */
+ private static Path locateRepoAsset(Path relative) {
+ for (Path dir = Path.of("").toAbsolutePath(); dir != null; dir = dir.getParent()) {
+ Path candidate = dir.resolve(relative);
+ if (Files.exists(candidate)) {
+ return candidate;
+ }
+ }
+ return relative;
+ }
private static final Color TITLE_COLOR = new Color(18, 40, 74);
private static final Color ACCENT_COLOR = new Color(37, 128, 197);
private static final Color MUTED_COLOR = new Color(86, 98, 120);
diff --git a/src/test/java/com/demcha/compose/engine/integration/TablePaginationIntegrationTest.java b/qa/src/test/java/com/demcha/compose/engine/integration/TablePaginationIntegrationTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/engine/integration/TablePaginationIntegrationTest.java
rename to qa/src/test/java/com/demcha/compose/engine/integration/TablePaginationIntegrationTest.java
diff --git a/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java b/qa/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java
rename to qa/src/test/java/com/demcha/testing/layout/LayoutSnapshotExtractorTest.java
diff --git a/src/test/java/com/demcha/testing/layout/LayoutSnapshotPublicApiDogfoodTest.java b/qa/src/test/java/com/demcha/testing/layout/LayoutSnapshotPublicApiDogfoodTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/layout/LayoutSnapshotPublicApiDogfoodTest.java
rename to qa/src/test/java/com/demcha/testing/layout/LayoutSnapshotPublicApiDogfoodTest.java
diff --git a/src/test/java/com/demcha/testing/visual/DebugNodeLabelsDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/DebugNodeLabelsDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/DebugNodeLabelsDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/DebugNodeLabelsDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/HeadingBarDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/HeadingBarDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/HeadingBarDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/HeadingBarDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/LayoutSnapshotRegressionDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/LayoutSnapshotRegressionDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/LayoutSnapshotRegressionDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/LayoutSnapshotRegressionDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/LineDashedDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/LineDashedDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/LineDashedDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/LineDashedDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/PathPrimitiveDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/PathPrimitiveDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/PathPrimitiveDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/PathPrimitiveDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/PdfVisualRegressionTest.java b/qa/src/test/java/com/demcha/testing/visual/PdfVisualRegressionTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/PdfVisualRegressionTest.java
rename to qa/src/test/java/com/demcha/testing/visual/PdfVisualRegressionTest.java
diff --git a/src/test/java/com/demcha/testing/visual/PublicVisualApiDogfoodTest.java b/qa/src/test/java/com/demcha/testing/visual/PublicVisualApiDogfoodTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/PublicVisualApiDogfoodTest.java
rename to qa/src/test/java/com/demcha/testing/visual/PublicVisualApiDogfoodTest.java
diff --git a/src/test/java/com/demcha/testing/visual/ShapeClipPathVisualTest.java b/qa/src/test/java/com/demcha/testing/visual/ShapeClipPathVisualTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/ShapeClipPathVisualTest.java
rename to qa/src/test/java/com/demcha/testing/visual/ShapeClipPathVisualTest.java
diff --git a/src/test/java/com/demcha/testing/visual/ShapeContainerPerCornerDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/ShapeContainerPerCornerDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/ShapeContainerPerCornerDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/ShapeContainerPerCornerDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/ShapeContainerVisualRegressionTest.java b/qa/src/test/java/com/demcha/testing/visual/ShapeContainerVisualRegressionTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/ShapeContainerVisualRegressionTest.java
rename to qa/src/test/java/com/demcha/testing/visual/ShapeContainerVisualRegressionTest.java
diff --git a/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/TableRowSpanDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/TextVerticalAlignDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/TextVerticalAlignDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/TextVerticalAlignDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/TextVerticalAlignDemoTest.java
diff --git a/src/test/java/com/demcha/testing/visual/TimelineDemoTest.java b/qa/src/test/java/com/demcha/testing/visual/TimelineDemoTest.java
similarity index 100%
rename from src/test/java/com/demcha/testing/visual/TimelineDemoTest.java
rename to qa/src/test/java/com/demcha/testing/visual/TimelineDemoTest.java
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_blue_banner.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_blue_banner.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_blue_banner.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_blue_banner.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_boxed_sections.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_boxed_sections.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_boxed_sections.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_boxed_sections.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_centered_headline.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_centered_headline.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_centered_headline.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_centered_headline.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_classic_serif.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_classic_serif.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_classic_serif.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_classic_serif.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_compact_mono.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_compact_mono.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_compact_mono.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_compact_mono.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_editorial_blue.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_editorial_blue.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_editorial_blue.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_editorial_blue.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_engineering_resume.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_engineering_resume.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_engineering_resume.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_engineering_resume.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_executive.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_executive.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_executive.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_executive.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_modern_professional.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_modern_professional.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_modern_professional.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_modern_professional.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_monogram_sidebar.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_monogram_sidebar.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_monogram_sidebar.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_monogram_sidebar.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_nordic_clean.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_nordic_clean.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_nordic_clean.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_nordic_clean.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_panel.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_panel.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_panel.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_panel.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_sidebar_portrait.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_sidebar_portrait.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_sidebar_portrait.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_sidebar_portrait.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_timeline_minimal.json b/qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_timeline_minimal.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_timeline_minimal.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/coverletter-v2/template_v2_letter_timeline_minimal.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_education_block.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_key_value_bold_key.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_prose_rejection_legacy_path.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/regression/boxed_sections_work_history_block.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_blue_banner.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_blue_banner.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_blue_banner.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_blue_banner.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_boxed_sections.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_centered_headline.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_centered_headline.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_centered_headline.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_centered_headline.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_classic_serif.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_classic_serif.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_classic_serif.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_classic_serif.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_compact_mono.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_compact_mono.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_compact_mono.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_compact_mono.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_editorial_blue.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_editorial_blue.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_editorial_blue.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_editorial_blue.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_engineering_resume.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_engineering_resume.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_engineering_resume.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_engineering_resume.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_executive.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_executive.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_executive.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_executive.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_modern_professional.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_modern_professional.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_modern_professional.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_modern_professional.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_monogram_sidebar.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_monogram_sidebar.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_monogram_sidebar.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_monogram_sidebar.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_nordic_clean.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_nordic_clean.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_nordic_clean.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_nordic_clean.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_panel.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_panel.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_panel.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_panel.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_sidebar_portrait.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_timeline_minimal.json b/qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_timeline_minimal.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_timeline_minimal.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/cv-v2/template_v2_cv_timeline_minimal.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/invoice/invoice_standard_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/invoice/invoice_standard_layout.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/invoice/invoice_standard_layout.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/invoice/invoice_standard_layout.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/proposal/proposal_long_layout.json b/qa/src/test/resources/layout-snapshots/canonical-templates/proposal/proposal_long_layout.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/proposal/proposal_long_layout.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/proposal/proposal_long_layout.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_changed_category_catalog.json b/qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_changed_category_catalog.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_changed_category_catalog.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_changed_category_catalog.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_standard.json b/qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_standard.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_standard.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_standard.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_with_additional_person.json b/qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_with_additional_person.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_with_additional_person.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_with_additional_person.json
diff --git a/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_without_metrics_footer.json b/qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_without_metrics_footer.json
similarity index 100%
rename from src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_without_metrics_footer.json
rename to qa/src/test/resources/layout-snapshots/canonical-templates/weekly-schedule/weekly_schedule_without_metrics_footer.json
diff --git a/src/test/resources/layout-snapshots/charts/grouped_bar_basic.json b/qa/src/test/resources/layout-snapshots/charts/grouped_bar_basic.json
similarity index 100%
rename from src/test/resources/layout-snapshots/charts/grouped_bar_basic.json
rename to qa/src/test/resources/layout-snapshots/charts/grouped_bar_basic.json
diff --git a/src/test/resources/layout-snapshots/charts/line_basic.json b/qa/src/test/resources/layout-snapshots/charts/line_basic.json
similarity index 100%
rename from src/test/resources/layout-snapshots/charts/line_basic.json
rename to qa/src/test/resources/layout-snapshots/charts/line_basic.json
diff --git a/src/test/resources/layout-snapshots/charts/line_monotone.json b/qa/src/test/resources/layout-snapshots/charts/line_monotone.json
similarity index 100%
rename from src/test/resources/layout-snapshots/charts/line_monotone.json
rename to qa/src/test/resources/layout-snapshots/charts/line_monotone.json
diff --git a/src/test/resources/layout-snapshots/document/canvas_layer_basic.json b/qa/src/test/resources/layout-snapshots/document/canvas_layer_basic.json
similarity index 100%
rename from src/test/resources/layout-snapshots/document/canvas_layer_basic.json
rename to qa/src/test/resources/layout-snapshots/document/canvas_layer_basic.json
diff --git a/src/test/resources/layout-snapshots/document/list_markers.json b/qa/src/test/resources/layout-snapshots/document/list_markers.json
similarity index 100%
rename from src/test/resources/layout-snapshots/document/list_markers.json
rename to qa/src/test/resources/layout-snapshots/document/list_markers.json
diff --git a/src/test/resources/layout-snapshots/document/nested_list_three_levels.json b/qa/src/test/resources/layout-snapshots/document/nested_list_three_levels.json
similarity index 100%
rename from src/test/resources/layout-snapshots/document/nested_list_three_levels.json
rename to qa/src/test/resources/layout-snapshots/document/nested_list_three_levels.json
diff --git a/src/test/resources/layout-snapshots/document/path_primitive.json b/qa/src/test/resources/layout-snapshots/document/path_primitive.json
similarity index 100%
rename from src/test/resources/layout-snapshots/document/path_primitive.json
rename to qa/src/test/resources/layout-snapshots/document/path_primitive.json
diff --git a/src/test/resources/layout-snapshots/document/table_cell_with_paragraph.json b/qa/src/test/resources/layout-snapshots/document/table_cell_with_paragraph.json
similarity index 100%
rename from src/test/resources/layout-snapshots/document/table_cell_with_paragraph.json
rename to qa/src/test/resources/layout-snapshots/document/table_cell_with_paragraph.json
diff --git a/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json b/qa/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json
similarity index 100%
rename from src/test/resources/layout-snapshots/fonts/available_fonts_preview.json
rename to qa/src/test/resources/layout-snapshots/fonts/available_fonts_preview.json
diff --git a/src/test/resources/layout-snapshots/integration/container_split.json b/qa/src/test/resources/layout-snapshots/integration/container_split.json
similarity index 100%
rename from src/test/resources/layout-snapshots/integration/container_split.json
rename to qa/src/test/resources/layout-snapshots/integration/container_split.json
diff --git a/src/test/resources/layout-snapshots/integration/features/barcode_showcase_render.json b/qa/src/test/resources/layout-snapshots/integration/features/barcode_showcase_render.json
similarity index 100%
rename from src/test/resources/layout-snapshots/integration/features/barcode_showcase_render.json
rename to qa/src/test/resources/layout-snapshots/integration/features/barcode_showcase_render.json
diff --git a/src/test/resources/layout-snapshots/integration/features/document_features_paginated.json b/qa/src/test/resources/layout-snapshots/integration/features/document_features_paginated.json
similarity index 100%
rename from src/test/resources/layout-snapshots/integration/features/document_features_paginated.json
rename to qa/src/test/resources/layout-snapshots/integration/features/document_features_paginated.json
diff --git a/src/test/resources/layout-snapshots/integration/massive_text.json b/qa/src/test/resources/layout-snapshots/integration/massive_text.json
similarity index 100%
rename from src/test/resources/layout-snapshots/integration/massive_text.json
rename to qa/src/test/resources/layout-snapshots/integration/massive_text.json
diff --git a/src/test/resources/layout-snapshots/integration/repository_showcase_render.json b/qa/src/test/resources/layout-snapshots/integration/repository_showcase_render.json
similarity index 100%
rename from src/test/resources/layout-snapshots/integration/repository_showcase_render.json
rename to qa/src/test/resources/layout-snapshots/integration/repository_showcase_render.json
diff --git a/src/test/resources/layout-snapshots/integration/table_pagination_test.json b/qa/src/test/resources/layout-snapshots/integration/table_pagination_test.json
similarity index 100%
rename from src/test/resources/layout-snapshots/integration/table_pagination_test.json
rename to qa/src/test/resources/layout-snapshots/integration/table_pagination_test.json
diff --git a/src/test/resources/layout-snapshots/shape-container/circle_with_text.json b/qa/src/test/resources/layout-snapshots/shape-container/circle_with_text.json
similarity index 100%
rename from src/test/resources/layout-snapshots/shape-container/circle_with_text.json
rename to qa/src/test/resources/layout-snapshots/shape-container/circle_with_text.json
diff --git a/src/test/resources/layout-snapshots/shape-container/ellipse_with_overlay.json b/qa/src/test/resources/layout-snapshots/shape-container/ellipse_with_overlay.json
similarity index 100%
rename from src/test/resources/layout-snapshots/shape-container/ellipse_with_overlay.json
rename to qa/src/test/resources/layout-snapshots/shape-container/ellipse_with_overlay.json
diff --git a/qa/src/test/resources/logback.xml b/qa/src/test/resources/logback.xml
new file mode 100644
index 000000000..ea732eb0f
--- /dev/null
+++ b/qa/src/test/resources/logback.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+ ${LOG_DIR}/app.log
+
+ ${LOG_DIR}/app-%d{yyyy-MM-dd}.log
+ 30
+ 1GB
+
+
+ INFO
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+ ${LOG_DIR}/warn.log
+
+ ${LOG_DIR}/warn-%d{yyyy-MM-dd}.log
+ 30
+ 512MB
+
+
+ WARN
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+ ${LOG_DIR}/error.log
+
+ ${LOG_DIR}/error-%d{yyyy-MM-dd}.log
+ 30
+ 512MB
+
+
+ ERROR
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+ %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%logger{36}) - %msg%n
+
+
+
+
+
+
+ WARN
+
+
+ %d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%logger{36}) - %msg%n
+
+
+
+
+
+ ${LOG_DIR}/layout-trace.log
+
+ ${LOG_DIR}/layout-trace-%d{yyyy-MM-dd}.log
+ 14
+ 512MB
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{48} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/blue_banner-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/boxed_sections-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/centered_headline-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/classic_serif-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/compact_mono-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/editorial_blue-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/engineering_resume-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/executive-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/mint-editorial-letter-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/mint-editorial-letter-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/mint-editorial-letter-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/mint-editorial-letter-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/modern_professional-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/monogram_sidebar-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/nordic_clean-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/panel-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/sidebar_portrait-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2-layered/timeline_minimal-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/blue_banner-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/blue_banner-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/blue_banner-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/blue_banner-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/boxed_sections-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/boxed_sections-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/boxed_sections-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/boxed_sections-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/centered_headline-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/centered_headline-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/centered_headline-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/centered_headline-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/classic_serif-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/classic_serif-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/classic_serif-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/classic_serif-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/compact_mono-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/compact_mono-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/compact_mono-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/compact_mono-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/editorial_blue-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/editorial_blue-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/editorial_blue-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/editorial_blue-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/engineering_resume-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/engineering_resume-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/engineering_resume-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/engineering_resume-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/executive-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/executive-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/executive-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/executive-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/modern_professional-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/modern_professional-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/modern_professional-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/modern_professional-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/monogram_sidebar-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/monogram_sidebar-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/monogram_sidebar-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/monogram_sidebar-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/nordic_clean-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/nordic_clean-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/nordic_clean-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/nordic_clean-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/panel-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/panel-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/panel-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/panel-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/sidebar_portrait-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/sidebar_portrait-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/sidebar_portrait-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/sidebar_portrait-page-0.png
diff --git a/src/test/resources/visual-baselines/coverletter-v2/timeline_minimal-page-0.png b/qa/src/test/resources/visual-baselines/coverletter-v2/timeline_minimal-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/coverletter-v2/timeline_minimal-page-0.png
rename to qa/src/test/resources/visual-baselines/coverletter-v2/timeline_minimal-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/blue_banner-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/boxed_sections-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/centered_headline-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/classic_serif-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/compact_mono-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/compact_mono-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/compact_mono-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/compact_mono-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/editorial_blue-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/engineering_resume-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/executive-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/executive-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/executive-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/minimal_underlined-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/mint_editorial-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/modern_professional-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/monogram_sidebar-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/nordic_clean-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/nordic_clean-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/nordic_clean-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/nordic_clean-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/panel-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/sidebar_portrait-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2-layered/timeline_minimal-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/blue_banner-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/blue_banner-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/blue_banner-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/blue_banner-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/boxed_sections-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/centered_headline-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/centered_headline-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/centered_headline-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/centered_headline-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/classic_serif-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/classic_serif-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/classic_serif-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/classic_serif-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/classic_serif-page-1.png b/qa/src/test/resources/visual-baselines/cv-v2/classic_serif-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/classic_serif-page-1.png
rename to qa/src/test/resources/visual-baselines/cv-v2/classic_serif-page-1.png
diff --git a/src/test/resources/visual-baselines/cv-v2/compact_mono-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/compact_mono-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/compact_mono-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/compact_mono-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/editorial_blue-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/editorial_blue-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/editorial_blue-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/editorial_blue-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/engineering_resume-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/engineering_resume-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/engineering_resume-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/engineering_resume-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/executive-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/executive-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/executive-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/executive-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/modern_professional-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/modern_professional-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/modern_professional-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/modern_professional-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/monogram_sidebar-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/nordic_clean-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/nordic_clean-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/nordic_clean-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/nordic_clean-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/panel-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/panel-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/panel-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/panel-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/sidebar_portrait-page-0.png
diff --git a/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png b/qa/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png
rename to qa/src/test/resources/visual-baselines/cv-v2/timeline_minimal-page-0.png
diff --git a/src/test/resources/visual-baselines/invoice-v2-layered/modern_invoice-page-0.png b/qa/src/test/resources/visual-baselines/invoice-v2-layered/modern_invoice-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/invoice-v2-layered/modern_invoice-page-0.png
rename to qa/src/test/resources/visual-baselines/invoice-v2-layered/modern_invoice-page-0.png
diff --git a/src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-0.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-0.png
rename to qa/src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-0.png
diff --git a/src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-1.png b/qa/src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-1.png
similarity index 100%
rename from src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-1.png
rename to qa/src/test/resources/visual-baselines/proposal-v2-layered/modern_proposal-page-1.png
diff --git a/src/test/resources/visual-baselines/shape-container-circle-with-text-page-0.png b/qa/src/test/resources/visual-baselines/shape-container-circle-with-text-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/shape-container-circle-with-text-page-0.png
rename to qa/src/test/resources/visual-baselines/shape-container-circle-with-text-page-0.png
diff --git a/src/test/resources/visual-baselines/shape-container-ellipse-overlay-page-0.png b/qa/src/test/resources/visual-baselines/shape-container-ellipse-overlay-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/shape-container-ellipse-overlay-page-0.png
rename to qa/src/test/resources/visual-baselines/shape-container-ellipse-overlay-page-0.png
diff --git a/src/test/resources/visual-baselines/shape-container-rounded-card-page-0.png b/qa/src/test/resources/visual-baselines/shape-container-rounded-card-page-0.png
similarity index 100%
rename from src/test/resources/visual-baselines/shape-container-rounded-card-page-0.png
rename to qa/src/test/resources/visual-baselines/shape-container-rounded-card-page-0.png
diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1
index 15c2360a9..8ab940507 100644
--- a/scripts/cut-release.ps1
+++ b/scripts/cut-release.ps1
@@ -302,22 +302,28 @@ function Run-ShowcaseSync {
# lifecycle phase. Wrapping the whole token in quotes preserves it
# as a single literal argument.
$execProp = '"-Dexec.mainClass=com.demcha.examples.support.ShowcaseSync"'
+ # The examples module depends on these bumped SNAPSHOT siblings (not on
+ # Central); each must be installed before exec:java can resolve them.
+ $exampleSnapshotSiblings = @('render-docx/pom.xml', 'testing/pom.xml')
if ($DryRun) {
Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -pl ." -ForegroundColor Yellow
+ foreach ($modulePom in $exampleSnapshotSiblings) {
+ Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -f $modulePom" -ForegroundColor Yellow
+ }
Write-Host " [DRY RUN] $mvnw -f examples/pom.xml exec:java $execProp" -ForegroundColor Yellow
return
}
Push-Location $repoRoot
try {
- # ShowcaseSync runs from the examples module, which depends on
- # io.github.demchaav:graphcompose:${project.version}. After Step 1
- # bumps the four pom.xml files to the new release version, that
- # artifact is not yet in the local m2 cache — only the previous
- # release is — so exec:java fails dependency resolution with
- # "Could not find artifact ...:graphcompose:jar:".
- # Install the root artifact first so the examples module can
- # resolve it. Bug surfaced during v1.6.5 cut: Step 4 aborted with
- # exit 1; we had to install by hand and resume manually.
+ # ShowcaseSync runs from the examples module, which depends on the
+ # engine plus the bumped SNAPSHOT siblings render-docx and
+ # graph-compose-testing. After Step 1 bumps the poms to the new release
+ # version, those artifacts are not yet in the local m2 cache — only the
+ # previous release is — so exec:java fails dependency resolution with
+ # "Could not find artifact ...:jar:". Install the engine and
+ # each sibling first so the examples module can resolve them. Bug
+ # surfaced during v1.6.5 cut: Step 4 aborted with exit 1; we had to
+ # install by hand and resume manually.
Write-Host " > $mvnw -B -ntp -DskipTests install -pl ." -ForegroundColor DarkGray
& $mvnw -B -ntp -DskipTests install -pl . 2>&1 | ForEach-Object {
if ($_ -match 'BUILD SUCCESS|BUILD FAILURE|ERROR') {
@@ -327,6 +333,17 @@ function Run-ShowcaseSync {
if ($LASTEXITCODE -ne 0) {
throw "Install root artifact failed (exit $LASTEXITCODE)"
}
+ foreach ($modulePom in $exampleSnapshotSiblings) {
+ Write-Host " > $mvnw -B -ntp -DskipTests install -f $modulePom" -ForegroundColor DarkGray
+ & $mvnw -B -ntp -DskipTests install -f $modulePom 2>&1 | ForEach-Object {
+ if ($_ -match 'BUILD SUCCESS|BUILD FAILURE|ERROR') {
+ Write-Host " $_" -ForegroundColor DarkGray
+ }
+ }
+ if ($LASTEXITCODE -ne 0) {
+ throw "Install $modulePom failed (exit $LASTEXITCODE)"
+ }
+ }
& $mvnw -f examples/pom.xml exec:java $execProp 2>&1 | ForEach-Object {
if ($_ -match 'Synced|Wrote manifest|BUILD SUCCESS|BUILD FAILURE|ERROR') {
Write-Host " $_" -ForegroundColor DarkGray
@@ -482,6 +499,11 @@ try {
# render-docx tracks the engine line (lockstep): its bumps here and
# its graph-compose dep is ${project.version} (follows automatically).
Update-PomVersion (Join-Path $repoRoot 'render-docx/pom.xml') $Version
+ # graph-compose-testing tracks the engine line (lockstep): its
+ # bumps here and its graph-compose dep is ${project.version} (follows
+ # automatically). graph-compose-qa is an aggregator child (its version is
+ # inherited) and is never published, so it needs no explicit bump.
+ Update-PomVersion (Join-Path $repoRoot 'testing/pom.xml') $Version
# Bundle tracks the engine line: its project bumps here; its
# graph-compose dep is ${project.version} (follows automatically) and its
# graph-compose-fonts dep is ${graphcompose.fonts.version} (stays pinned —
@@ -551,6 +573,7 @@ try {
'aggregator/pom.xml',
'bundle/pom.xml',
'render-docx/pom.xml',
+ 'testing/pom.xml',
'examples/pom.xml',
'benchmarks/pom.xml',
'README.md',
diff --git a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java
index 5a4fd50d1..955d64946 100644
--- a/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java
+++ b/src/test/java/com/demcha/documentation/VersionConsistencyGuardTest.java
@@ -61,6 +61,12 @@ void everyModuleResolvesToTheRootProjectVersion() throws Exception {
assertThat(effectiveVersion(PROJECT_ROOT.resolve("bundle/pom.xml")))
.describedAs("bundle (graph-compose-bundle) tracks the engine line and must equal the root version (%s)", root)
.isEqualTo(root);
+ assertThat(effectiveVersion(PROJECT_ROOT.resolve("render-docx/pom.xml")))
+ .describedAs("render-docx (graph-compose-render-docx) tracks the engine line and must equal the root version (%s)", root)
+ .isEqualTo(root);
+ assertThat(effectiveVersion(PROJECT_ROOT.resolve("testing/pom.xml")))
+ .describedAs("testing (graph-compose-testing) tracks the engine line and must equal the root version (%s)", root)
+ .isEqualTo(root);
// NOTE: fonts/pom.xml (graph-compose-fonts) is intentionally NOT checked
// here. It carries an independent version line (it ships on its own
@@ -92,6 +98,9 @@ void childModulesInheritVersionInsteadOfDeclaringTheirOwn() throws Exception {
assertThat(declaresOwnVersion(PROJECT_ROOT.resolve("benchmarks/pom.xml")))
.describedAs("benchmarks/pom.xml must inherit from the aggregator parent, not declare its own")
.isFalse();
+ assertThat(declaresOwnVersion(PROJECT_ROOT.resolve("qa/pom.xml")))
+ .describedAs("qa/pom.xml must inherit from the aggregator parent, not declare its own")
+ .isFalse();
}
@Test
diff --git a/testing/pom.xml b/testing/pom.xml
new file mode 100644
index 000000000..7f9925be2
--- /dev/null
+++ b/testing/pom.xml
@@ -0,0 +1,239 @@
+
+
+ 4.0.0
+
+
+ io.github.demchaav
+ graph-compose-testing
+ 2.0.0-SNAPSHOT
+
+ GraphCompose Testing
+ Consumer testing support for GraphCompose: layout-snapshot assertions and PDF visual regression.
+ https://github.com/DemchaAV/GraphCompose
+
+
+
+ MIT License
+ https://opensource.org/licenses/MIT
+ repo
+
+
+
+
+
+ DemchaAV
+ Artem Demchyshyn
+ demchishynartem@gmail.com
+ https://github.com/DemchaAV
+
+ Lead Developer
+ Architect
+
+ UTC 0
+
+
+
+
+ scm:git:https://github.com/DemchaAV/GraphCompose.git
+ scm:git:ssh://git@github.com/DemchaAV/GraphCompose.git
+ https://github.com/DemchaAV/GraphCompose/tree/main
+
+
+
+ UTF-8
+ 17
+
+ 3.0.7
+ 2.21.4
+ 6.1.1
+ 3.27.7
+ 1.5.37
+
+ 3.15.0
+ 3.5.0
+ 3.5.6
+ 3.4.0
+ 3.12.0
+ 3.2.8
+ 0.11.0
+
+
+ true
+
+
+
+
+ io.github.demchaav
+ graph-compose
+ ${project.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.bom.version}
+
+
+ org.apache.pdfbox
+ pdfbox
+ ${pdfbox.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.bom.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.plugin.version}
+
+ ${maven.compiler.release}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.surefire.plugin.version}
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.jar.plugin.version}
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven.javadoc.plugin.version}
+
+ com.demcha.compose.testing
+ all
+
+
+
+ validate-public-api-javadoc
+ verify
+
+ javadoc
+
+
+
+
+
+
+
+
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven.source.plugin.version}
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven.javadoc.plugin.version}
+
+
+ attach-javadocs
+ package
+
+ jar
+
+
+ com.demcha.compose.testing
+ all
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven.gpg.plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+ ${gpg.skip}
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ ${central.publishing.plugin.version}
+ true
+
+ central
+ false
+ validated
+
+
+
+
+
+
+
diff --git a/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertions.java b/testing/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertions.java
similarity index 100%
rename from src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertions.java
rename to testing/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertions.java
diff --git a/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotJson.java b/testing/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotJson.java
similarity index 100%
rename from src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotJson.java
rename to testing/src/main/java/com/demcha/compose/testing/layout/LayoutSnapshotJson.java
diff --git a/src/main/java/com/demcha/compose/testing/layout/package-info.java b/testing/src/main/java/com/demcha/compose/testing/layout/package-info.java
similarity index 100%
rename from src/main/java/com/demcha/compose/testing/layout/package-info.java
rename to testing/src/main/java/com/demcha/compose/testing/layout/package-info.java
diff --git a/src/main/java/com/demcha/compose/testing/visual/ImageDiff.java b/testing/src/main/java/com/demcha/compose/testing/visual/ImageDiff.java
similarity index 100%
rename from src/main/java/com/demcha/compose/testing/visual/ImageDiff.java
rename to testing/src/main/java/com/demcha/compose/testing/visual/ImageDiff.java
diff --git a/src/main/java/com/demcha/compose/testing/visual/PdfVisualRegression.java b/testing/src/main/java/com/demcha/compose/testing/visual/PdfVisualRegression.java
similarity index 100%
rename from src/main/java/com/demcha/compose/testing/visual/PdfVisualRegression.java
rename to testing/src/main/java/com/demcha/compose/testing/visual/PdfVisualRegression.java
diff --git a/src/main/java/com/demcha/compose/testing/visual/package-info.java b/testing/src/main/java/com/demcha/compose/testing/visual/package-info.java
similarity index 100%
rename from src/main/java/com/demcha/compose/testing/visual/package-info.java
rename to testing/src/main/java/com/demcha/compose/testing/visual/package-info.java
diff --git a/src/test/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertionsTest.java b/testing/src/test/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertionsTest.java
similarity index 100%
rename from src/test/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertionsTest.java
rename to testing/src/test/java/com/demcha/compose/testing/layout/LayoutSnapshotAssertionsTest.java