Skip to content

Commit b27b21f

Browse files
authored
make single-file reports truly self-contained (via #3354)
1 parent cf191ff commit b27b21f

8 files changed

Lines changed: 132 additions & 752 deletions

File tree

allure-generator/src/main/java/io/qameta/allure/core/ReportWebGenerator.java

Lines changed: 56 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class ReportWebGenerator {
5353
private static final String IMAGE_X_ICON = "image/x-icon";
5454
private static final String TEXT_JAVASCRIPT = "text/javascript; charset=utf-8";
5555
private static final String TEXT_CSS = "text/css; charset=utf-8";
56+
private static final String READ_FILE_ERROR_MESSAGE = "Can't read file ";
5657

5758
private static final JsonMapper JSON_MAPPER = JsonMapper.builder().build();
5859
private static final TypeReference<Map<String, ViteManifestEntry>> VITE_MANIFEST_TYPE = new TypeReference<Map<String, ViteManifestEntry>>() {
@@ -63,70 +64,34 @@ public void generate(final Configuration configuration,
6364
final Path outputDirectory) {
6465

6566
final boolean inline = reportStorage instanceof InMemoryReportStorage;
66-
6767
final Map<String, ViteManifestEntry> viteManifest = loadViteManifest();
6868
final List<String> entryFiles = getEntryFiles(viteManifest);
6969
final List<String> styleFiles = getStyleFiles(viteManifest);
7070
final List<String> producedFiles = getProducedFiles(viteManifest);
7171
final String faviconFile = requireFaviconFile(viteManifest);
7272

7373
final List<String> coreStyleUrls = inline
74-
? inlineWebStyles(styleFiles)
74+
? inlineWebFiles(TEXT_CSS, styleFiles)
7575
: new ArrayList<>(styleFiles);
7676
if (!inline) {
7777
copyWebResources(outputDirectory, producedFiles);
7878
}
7979
final List<String> coreJsUrls = inline
80-
? inlineWebScripts(entryFiles)
80+
? inlineWebFiles(TEXT_JAVASCRIPT, entryFiles)
8181
: new ArrayList<>(entryFiles);
8282

8383
final List<String> pluginJsUrls = new ArrayList<>();
8484
final List<String> pluginStyleUrls = new ArrayList<>();
85-
configuration.getPlugins().forEach(plugin -> {
86-
final Map<String, Path> pluginFiles = new HashMap<>(plugin.getPluginFiles());
87-
88-
final PluginConfiguration config = plugin.getConfig();
89-
config.getJsFiles().forEach(jsFile -> {
90-
final Path jsFilePath = pluginFiles.remove(jsFile);
91-
if (inline) {
92-
pluginJsUrls.add(
93-
dataBase64(
94-
TEXT_JAVASCRIPT,
95-
jsFilePath
96-
)
97-
);
98-
} else {
99-
final String key = pluginFileKey(config, jsFile);
100-
pluginJsUrls.add(key);
101-
write(outputDirectory, key, jsFilePath);
102-
}
103-
});
104-
105-
config.getCssFiles().forEach(cssFile -> {
106-
final Path cssFilePath = pluginFiles.remove(cssFile);
107-
if (inline) {
108-
pluginStyleUrls.add(
109-
dataBase64(
110-
TEXT_CSS,
111-
cssFilePath
112-
)
113-
);
114-
} else {
115-
final String key = pluginFileKey(config, cssFile);
116-
pluginStyleUrls.add(key);
117-
write(outputDirectory, key, cssFilePath);
118-
}
119-
});
120-
121-
pluginFiles.forEach((key, path) -> {
122-
final String pluginFileKey = pluginFileKey(config, key);
123-
write(
85+
configuration.getPlugins().forEach(
86+
plugin -> processPlugin(
87+
plugin,
88+
inline,
89+
reportStorage,
12490
outputDirectory,
125-
pluginFileKey,
126-
path
127-
);
128-
});
129-
});
91+
pluginJsUrls,
92+
pluginStyleUrls
93+
)
94+
);
13095

13196
final FreemarkerContext context = configuration.requireContext(FreemarkerContext.class);
13297

@@ -147,12 +112,10 @@ public void generate(final Configuration configuration,
147112
dataModel.put("pluginJsUrls", pluginJsUrls);
148113

149114
if (inline) {
150-
final Map<String, String> reportDataFiles = new HashMap<>(
151-
((InMemoryReportStorage) reportStorage)
152-
.getReportDataFiles()
115+
dataModel.put(
116+
"reportDataFiles",
117+
((InMemoryReportStorage) reportStorage).getReportDataFiles()
153118
);
154-
155-
dataModel.put("reportDataFiles", reportDataFiles);
156119
}
157120

158121
final boolean analyticsDisable = Optional.ofNullable(System.getenv(Constants.NO_ANALYTICS))
@@ -222,20 +185,52 @@ private static List<String> getProducedFiles(final Map<String, ViteManifestEntry
222185
return new ArrayList<>(producedFiles);
223186
}
224187

225-
private static List<String> inlineWebStyles(final List<String> styleFiles) {
226-
final List<String> inlineStyles = new ArrayList<>();
188+
private static List<String> inlineWebFiles(final String contentType,
189+
final List<String> entryFiles) {
190+
final List<String> scriptUrls = new ArrayList<>();
227191

228-
styleFiles.forEach(styleFile -> inlineStyles.add(dataBase64(TEXT_CSS, styleFile)));
192+
entryFiles.forEach(entryFile -> scriptUrls.add(dataBase64(contentType, entryFile)));
229193

230-
return inlineStyles;
194+
return scriptUrls;
231195
}
232196

233-
private static List<String> inlineWebScripts(final List<String> entryFiles) {
234-
final List<String> scriptUrls = new ArrayList<>();
197+
private static void processPlugin(final Plugin plugin,
198+
final boolean inline,
199+
final ReportStorage reportStorage,
200+
final Path outputDirectory,
201+
final List<String> pluginJsUrls,
202+
final List<String> pluginStyleUrls) {
203+
final Map<String, Path> pluginFiles = new HashMap<>(plugin.getPluginFiles());
204+
final PluginConfiguration config = plugin.getConfig();
205+
206+
config.getJsFiles().forEach(jsFile -> {
207+
final Path jsFilePath = pluginFiles.remove(jsFile);
208+
if (inline) {
209+
pluginJsUrls.add(dataBase64(TEXT_JAVASCRIPT, jsFilePath));
210+
} else {
211+
final String key = pluginFileKey(config, jsFile);
212+
pluginJsUrls.add(key);
213+
write(outputDirectory, key, jsFilePath);
214+
}
215+
});
235216

236-
entryFiles.forEach(entryFile -> scriptUrls.add(dataBase64(TEXT_JAVASCRIPT, entryFile)));
217+
config.getCssFiles().forEach(cssFile -> {
218+
final Path cssFilePath = pluginFiles.remove(cssFile);
219+
if (inline) {
220+
pluginStyleUrls.add(dataBase64(TEXT_CSS, cssFilePath));
221+
} else {
222+
final String key = pluginFileKey(config, cssFile);
223+
pluginStyleUrls.add(key);
224+
write(outputDirectory, key, cssFilePath);
225+
}
226+
});
237227

238-
return scriptUrls;
228+
if (inline) {
229+
final InMemoryReportStorage storage = (InMemoryReportStorage) reportStorage;
230+
pluginFiles.forEach((key, path) -> storage.addDataFile(pluginFileKey(config, key), path));
231+
} else {
232+
pluginFiles.forEach((key, path) -> write(outputDirectory, pluginFileKey(config, key), path));
233+
}
239234
}
240235

241236
private static String pluginFileKey(final PluginConfiguration config, final String cssFile) {
@@ -327,7 +322,7 @@ private static String dataBase64(final String contentType, final Path path) {
327322
final byte[] bytes = FileUtils.readFileToByteArray(path.toFile());
328323
return dataUrl(contentType, bytes);
329324
} catch (IOException e) {
330-
throw new ReportGenerationException("Can't read file " + path, e);
325+
throw new ReportGenerationException(READ_FILE_ERROR_MESSAGE + path, e);
331326
}
332327
}
333328

allure-generator/src/test/java/io/qameta/allure/core/ReportWebGeneratorTest.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@
1818
import io.qameta.allure.Allure;
1919
import io.qameta.allure.ConfigurationBuilder;
2020
import io.qameta.allure.Description;
21+
import io.qameta.allure.PluginConfiguration;
2122
import io.qameta.allure.ReportStorage;
23+
import io.qameta.allure.plugin.DefaultPlugin;
2224
import org.junit.jupiter.api.Test;
2325
import org.junit.jupiter.api.io.TempDir;
2426
import org.junitpioneer.jupiter.SetEnvironmentVariable;
2527

28+
import java.io.IOException;
2629
import java.nio.charset.StandardCharsets;
2730
import java.nio.file.Files;
2831
import java.nio.file.Path;
32+
import java.util.Base64;
33+
import java.util.Collections;
34+
import java.util.List;
35+
import java.util.stream.Collectors;
36+
import java.util.stream.Stream;
2937

3038
import static org.assertj.core.api.Assertions.assertThat;
3139

@@ -188,6 +196,61 @@ void shouldInlineHashedScriptsInSingleFileMode(@TempDir final Path tempDirectory
188196
.doesNotContain("type=\"importmap\"");
189197
}
190198

199+
/**
200+
* Verifies embedding undeclared plugin static files in single file mode for web report generation.
201+
*
202+
* @throws IOException if plugin fixture files could not be written.
203+
*/
204+
@Description
205+
@Test
206+
void shouldInlinePluginStaticFilesAsReportDataInSingleFileMode(
207+
@TempDir final Path tempDirectory)
208+
throws IOException {
209+
final Path pluginDirectory = tempDirectory.resolve("screen-diff-plugin");
210+
final Path pluginStaticDirectory = pluginDirectory.resolve("static");
211+
final Path outputDirectory = tempDirectory.resolve("report");
212+
final String script = "console.log('screen-diff');";
213+
final String stylesheet = ".screen-diff {}";
214+
215+
Files.createDirectories(pluginStaticDirectory);
216+
Files.writeString(
217+
pluginStaticDirectory.resolve("index.js"),
218+
script,
219+
StandardCharsets.UTF_8
220+
);
221+
Files.writeString(pluginStaticDirectory.resolve("styles.css"), stylesheet, StandardCharsets.UTF_8);
222+
223+
final PluginConfiguration pluginConfiguration = new PluginConfiguration()
224+
.setId("screen-diff");
225+
final Configuration configuration = ConfigurationBuilder.empty()
226+
.withPlugins(
227+
List.of(
228+
new DefaultPlugin(
229+
pluginConfiguration,
230+
Collections.emptyList(),
231+
pluginDirectory
232+
)
233+
)
234+
)
235+
.build();
236+
237+
generateReport(configuration, new InMemoryReportStorage(), outputDirectory);
238+
239+
final Path indexHtml = outputDirectory.resolve("index.html");
240+
final String encodedScript = Base64.getEncoder()
241+
.encodeToString(script.getBytes(StandardCharsets.UTF_8));
242+
final String encodedStylesheet = Base64.getEncoder()
243+
.encodeToString(stylesheet.getBytes(StandardCharsets.UTF_8));
244+
245+
assertThat(listRelativeFiles(outputDirectory))
246+
.containsExactly("index.html");
247+
assertThat(indexHtml)
248+
.isRegularFile()
249+
.content(StandardCharsets.UTF_8)
250+
.contains("d('plugin/screen-diff/index.js','" + encodedScript + "')")
251+
.contains("d('plugin/screen-diff/styles.css','" + encodedStylesheet + "')");
252+
}
253+
191254
private void generateReport(
192255
final Configuration configuration,
193256
final ReportStorage reportStorage,
@@ -202,4 +265,15 @@ private void generateReport(
202265
);
203266
});
204267
}
268+
269+
private List<String> listRelativeFiles(final Path outputDirectory) throws IOException {
270+
try (Stream<Path> files = Files.walk(outputDirectory)) {
271+
return files
272+
.filter(Files::isRegularFile)
273+
.map(outputDirectory::relativize)
274+
.map(Path::toString)
275+
.sorted()
276+
.collect(Collectors.toList());
277+
}
278+
}
205279
}

0 commit comments

Comments
 (0)