From xwiki
Convert XWiki functional IT tests from the JUnit4 legacy framework (xwiki-platform-test-ui) to the JUnit5 Docker-based framework (@UITest). Covers module setup, @UITest configuration, WAR composition, repository setup, and common pitfalls.
How this skill is triggered — by the user, by Claude, or both
Slash command
/xwiki:convert-tests-dockerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when converting existing tests that use the JUnit4 legacy functional test framework (based on `AbstractAuthenticatedTest`, `getUtil()`, `getRepositoryTestUtils()`, etc. from `xwiki-platform-test-ui`) to JUnit5 Docker-based tests using `@UITest`.
Use this skill when converting existing tests that use the JUnit4 legacy functional test framework (based on AbstractAuthenticatedTest, getUtil(), getRepositoryTestUtils(), etc. from xwiki-platform-test-ui) to JUnit5 Docker-based tests using @UITest.
For converting unit tests (JUnit4/JMock → JUnit5/Mockito), use the convert-tests skill instead.
Create a new Maven module alongside the existing test module (e.g. xwiki-platform-extension-test-docker):
src/
test/
it/org/xwiki/<feature>/test/docker/
AllIT.java ← test suite entry point
FeatureIT.java ← test class
resources/
packagefile/ ← any test resource files (XAR packages, etc.)
webapp/ ← optional: files overlaid onto the test WAR (see "Overriding WAR files")
package org.xwiki.<feature>.test.docker;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;
import org.xwiki.test.docker.junit5.DockerTestSuite;
@Suite
@SuiteDisplayName("Feature Tests (Docker)")
@DockerTestSuite
public class AllIT
{
}
<packaging>jar</packaging>
<dependencies>
<!-- The feature's page objects module -->
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-<feature>-test-pageobjects</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- Docker test framework -->
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-test-docker</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
</plugins>
</build>
Add a docker profile to the parent test module's pom.xml:
<profiles>
<profile>
<id>docker</id>
<modules>
<module>xwiki-platform-<feature>-test-docker</module>
</modules>
</profile>
</profiles>
The @UITest annotation on the IT class configures the Docker test environment:
@UITest(
extraJARs = {
// Add JARs needed at WAR startup that aren't in the minimal WAR by default.
// The minimal WAR only contains the bare minimum — runtime deps in pom.xml
// are NOT automatically added to WEB-INF/lib.
"org.xwiki.commons:xwiki-commons-extension-repository-xwiki",
"org.xwiki.platform:xwiki-platform-extension-index"
},
properties = {
// Append properties to xwiki.properties (does NOT replace defaults).
// Use this to configure extension repositories, feature flags, etc.
"xwikiPropertiesAdditionalProperties=extension.core.resolve=false\n"
+ "extension.repositories = self:xwiki:http://localhost:8080/xwiki/rest"
}
)
class FeatureIT { ... }
| Parameter | Purpose |
|---|---|
extraJARs | JARs added to WEB-INF/lib. Use for components needed at startup (e.g. repository factories, index providers). Format: "groupId:artifactId" |
properties | Entries added to TestConfiguration. xwikiPropertiesAdditionalProperties appends lines to xwiki.properties |
offline | When true, configures Maven in offline mode; only maven-local repository is set in xwiki.properties |
xwikiExtensionRepositories | REPLACES the full extension repository list. Prefer xwikiPropertiesAdditionalProperties to APPEND repositories instead |
To use BOTH the local Maven repo AND the XWiki self-hosted repo (for test extensions served via REST), use xwikiPropertiesAdditionalProperties to append the self repo. Apache Commons Configuration2 accumulates duplicate extension.repositories keys into a list:
# Generated by offline=true:
extension.repositories = maven-local:maven:file:///~/.m2/repository
# Appended via xwikiPropertiesAdditionalProperties:
extension.repositories = self:xwiki:http://localhost:8080/xwiki/rest
Why not xwikiExtensionRepositories? It's not in KNOWN_LIST_KEYS so it replaces rather than appends. xwikiPropertiesAdditionalProperties always appends.
WARBuilder builds a minimal WAR from xwiki-platform-minimaldependencies. What this means:
pom.xml. These are meant to be installed as extensions at runtime.minimaldependencies plus any extraJARs from @UITest.Common extraJARs needed for extension-related tests:
org.xwiki.commons:xwiki-commons-extension-repository-xwiki — provides XWikiExtensionRepositoryFactory for the xwiki: repository type. Without it: startup error "Unsupported repository type [xwiki]".org.xwiki.platform:xwiki-platform-extension-index — Solr-backed extension search. Without it: NullPointerException in Solr client, keyword searches return 0 results.The WARBuilder assembles the test WAR by:
minimaldependencies (including xwiki-platform-web-war)WEB-INF/libsrc/test/webapp/ in the test moduleTo override a template or resource in the test WAR, place the file under src/test/webapp/ with the same relative path as in the deployed WAR:
src/test/webapp/templates/my_template.vm → webapps/xwiki/templates/my_template.vm
This is the clean way to test against modified templates without rebuilding the entire distribution.
When you modify a template in xwiki-platform-web-templates, you must also rebuild xwiki-platform-web-war for the change to appear in the Docker test WAR:
mvn clean install -pl xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates \
-Plegacy,snapshot -DskipTests
mvn clean install -pl xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-war \
-Plegacy,snapshot -DskipTests
The Docker test's mvn clean verify then picks up the updated xwiki-platform-web-war JAR from the local Maven repo.
@UITest(...)
class FeatureIT
{
// Static fields shared across all tests in this class
private static RepositoryTestUtils repositoryTestUtils;
private static ExtensionTestUtils extensionTestUtils;
@BeforeAll
static void beforeAll(TestUtils setup) throws Exception
{
setup.loginAsSuperAdmin();
setup.recacheSecretToken();
setup.setDefaultCredentials(TestUtils.SUPER_ADMIN_CREDENTIALS);
repositoryTestUtils = new RepositoryTestUtils(setup, new RepositoryUtils(), new SolrTestUtils(setup));
repositoryTestUtils.init(); // generates test extension files
extensionTestUtils = new ExtensionTestUtils(setup);
}
@BeforeEach
void setUp(TestUtils setup) throws Exception
{
setup.loginAsSuperAdmin();
// Cleanup state from previous test
extensionTestUtils.uninstall("my-extension");
repositoryTestUtils.deleteExtension("my-extension");
repositoryTestUtils.waitUntilReady();
// Verify clean state
ExtensionsSearchResult result = setup.rest().getResource("repository/search", ...);
assertEquals(0, result.getTotalHits());
}
@Test
@Order(1)
void testSomething(TestUtils setup) throws Exception
{
// ...
}
}
Key differences from JUnit4:
TestUtils setup is injected as a method parameter, not accessed via getUtil()@BeforeAll static methods also receive TestUtils as a parameter@Order controls test execution order (important: tests often depend on previous state)| JUnit4 | JUnit5 Docker |
|---|---|
getUtil() | setup (injected parameter) |
getRepositoryTestUtils() | repositoryTestUtils (static field, initialized in @BeforeAll) |
getExtensionTestUtils() | extensionTestUtils (static field) |
// Installing an extension programmatically (bypassing UI)
extensionTestUtils.install(new ExtensionId("my-extension", "1.0"));
// Uninstalling
extensionTestUtils.uninstall("my-extension");
// Wait for Solr index to be ready after adding test extensions
repositoryTestUtils.waitUntilReady();
TestExtension extension = repositoryTestUtils.getTestExtension(
new ExtensionId("alice-xar-extension", "1.3"), "xar");
// Optionally override the auto-set name (defaults to artifact ID):
extension.setName("Alice Wiki Macro");
repositoryTestUtils.addExtension(extension);
TestExtension constructor sets name to id.getId() (the artifact ID string). Override with setName() when tests assert on the display name.
As of commit 891f2b563011 (XWIKI-24145), the log collapse toggle changed from <label> to <button>. The XPath in ExtensionProgressPane.getJobLogLabel() must use /button:
// Correct for current codebase:
String xpath = "//*[@class = 'log']/parent::dd/preceding-sibling::dt[last()]/button";
Any <button class="collapse-toggle"> inside an extension <form class="extension-item"> must have type="button". Without it, the button defaults to type="submit" and clicking it submits the extension form, causing a DOM replacement and StaleElementReferenceException in tests that access elements after the click.
Template: job_macros.vm, macro displayJobStatusLog:
<button type="button" class="btn btn-default btn-xs collapse-toggle...">
The minimal Docker WAR contains a different set of core extensions than a full XWiki installation. For example, it contains org.apache.groovy:groovy (name: "Apache Groovy") as the groovy runtime, not xwiki-platform-groovy or xwiki-commons-groovy. Adjust assertions accordingly:
// Full XWiki: > 1 groovy extensions (XWiki wrappers + groovy itself)
// Minimal Docker WAR: only 1 groovy-related extension
assertTrue(searchResults.getDisplayedResultsCount() >= 1);
# Full run including Docker functional tests
mvn clean verify \
-pl xwiki-platform-core/xwiki-platform-<feature>/xwiki-platform-<feature>-test/xwiki-platform-<feature>-test-docker \
-Plegacy,integration-tests,snapshot,docker \
-Dxwiki.checkstyle.skip=true \
-Dxwiki.surefire.captureconsole.skip=true \
-Dxwiki.revapi.skip=true
The docker profile is needed to activate the test module (see parent pom.xml configuration above).
Results in: target/failsafe-reports/TEST-*.xml
Screenshots on failure in: target/screenshots/ or in the test output directory named after the test configuration.
mvn clean verify with the docker profile and confirm 0 failures, 0 errors.@Order values cover the expected dependency chain between tests.@BeforeEach cleanup uninstalls/deletes all state that tests create, so tests are independent.npx claudepluginhub xwiki/xwiki-dev-llm --plugin xwikiProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.