forked from testcontainers/testcontainers-node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselenium-container.ts
More file actions
139 lines (117 loc) · 4.67 KB
/
selenium-container.ts
File metadata and controls
139 lines (117 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { copyFile } from "fs/promises";
import path from "path";
import { pipeline } from "stream/promises";
import {
AbstractStartedContainer,
AbstractStoppedContainer,
GenericContainer,
log,
Network,
StartedNetwork,
StartedTestContainer,
StopOptions,
StoppedTestContainer,
Wait,
} from "testcontainers";
import tmp from "tmp";
const SELENIUM_PORT = 4444;
const VNC_PORT = 5900;
const SELENIUM_NETWORK_ALIAS = "selenium";
export const SELENIUM_VIDEO_IMAGE = "selenium/video:ffmpeg-4.3.1-20230508";
export class SeleniumContainer extends GenericContainer {
constructor(image: string) {
super(image);
this.withExposedPorts(SELENIUM_PORT, VNC_PORT)
.withSharedMemorySize(2 * 1024 * 1024 * 1024)
.withWaitStrategy(
Wait.forAll([
Wait.forListeningPorts(),
Wait.forHttp("/wd/hub/status", SELENIUM_PORT).forResponsePredicate((response) => {
try {
return JSON.parse(response).value.ready;
} catch {
return false;
}
}),
])
);
}
public withRecording(): SeleniumRecordingContainer {
return new SeleniumRecordingContainer(this.imageName.string);
}
override async start(): Promise<StartedSeleniumContainer> {
return new StartedSeleniumContainer(await super.start());
}
}
export class StartedSeleniumContainer extends AbstractStartedContainer {
private readonly serverUrl: string;
constructor(startedTestContainer: StartedTestContainer) {
super(startedTestContainer);
this.serverUrl = `http://${this.getHost()}:${this.getMappedPort(4444)}/wd/hub`;
}
getServerUrl(): string {
return this.serverUrl;
}
override async stop(options?: Partial<StopOptions>): Promise<StoppedSeleniumContainer> {
return new StoppedSeleniumContainer(await super.stop(options));
}
}
export class StoppedSeleniumContainer extends AbstractStoppedContainer {
constructor(private readonly stoppedSeleniumContainer: StoppedTestContainer) {
super(stoppedSeleniumContainer);
}
}
export class SeleniumRecordingContainer extends SeleniumContainer {
constructor(image: string) {
super(image);
}
public override async start(): Promise<StartedSeleniumRecordingContainer> {
const network = await new Network().start();
this.withNetwork(network);
this.withNetworkAliases(SELENIUM_NETWORK_ALIAS);
const startedSeleniumContainer = await super.start();
const startedFfmpegContainer = await new GenericContainer(SELENIUM_VIDEO_IMAGE)
.withNetwork(network)
.withEnvironment({ DISPLAY_CONTAINER_NAME: SELENIUM_NETWORK_ALIAS })
.withWaitStrategy(Wait.forLogMessage(/.*video-recording entered RUNNING state.*/))
.start();
return new StartedSeleniumRecordingContainer(startedSeleniumContainer, startedFfmpegContainer, network);
}
}
export class StartedSeleniumRecordingContainer extends StartedSeleniumContainer {
constructor(
startedSeleniumContainer: StartedTestContainer,
private readonly startedFfmpegContainer: StartedTestContainer,
private readonly network: StartedNetwork
) {
super(startedSeleniumContainer);
}
override async stop(options?: Partial<StopOptions>): Promise<StoppedSeleniumRecordingContainer> {
const stoppedSeleniumContainer = await super.stop(options);
const stoppedFfmpegContainer = await this.startedFfmpegContainer.stop({ remove: false, timeout: 60_000 });
await this.network.stop();
return new StoppedSeleniumRecordingContainer(stoppedSeleniumContainer, stoppedFfmpegContainer);
}
}
export class StoppedSeleniumRecordingContainer extends StoppedSeleniumContainer {
constructor(
stoppedSeleniumContainer: StoppedTestContainer,
private readonly stoppedFfmpegContainer: StoppedTestContainer
) {
super(stoppedSeleniumContainer);
}
async saveRecording(target: string): Promise<void> {
const ffmpegContainerId = this.stoppedFfmpegContainer.getId();
log.debug("Extracting archive from container...", { containerId: ffmpegContainerId });
const archiveStream = await this.stoppedFfmpegContainer.copyArchiveFromContainer("/videos/video.mp4");
log.debug("Extracted archive from container", { containerId: ffmpegContainerId });
log.debug("Unpacking archive...", { containerId: ffmpegContainerId });
const destinationDir = tmp.dirSync({ keep: false });
const { unpackTar } = await import("modern-tar/fs");
await pipeline(archiveStream, unpackTar(destinationDir.name));
log.debug("Unpacked archive", { containerId: ffmpegContainerId });
const videoFile = path.resolve(destinationDir.name, "video.mp4");
await copyFile(videoFile, target);
log.debug(`Extracted video to "${target}"`, { containerId: ffmpegContainerId });
}
}