sefiutils 1.0.46 → 1.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ import dotenv from "dotenv";
2
+ dotenv.config() // read ".env"
3
+
4
+ import path from "path";
5
+ import fs, { writeFileSync } from "fs";
6
+ import { debug, verbose } from "./log";
7
+ import { getRemoteAddressAndPort } from "./seUtils";
8
+ import { homedir } from "os";
9
+
10
+ declare global {
11
+ var server: any; // only loaded in remote case; this global variable is only visible in globalTeardown.ts
12
+ // and not in the tests, cf. https://jestjs.io/docs/configuration#globalsetup-string
13
+ }
14
+ /**
15
+ * This function is called by Jest once before all tests are executed.
16
+ * In order to get called, it must be referenced in the jest configuration file:
17
+ * ```
18
+ * "globalSetup": "<rootDir>/tests/globalSetup.ts"
19
+ * ```
20
+ *
21
+ * **Important Note**: "Any global variables that are defined through globalSetup can only be
22
+ * read in globalTeardown. You cannot retrieve globals defined here in your test suites."
23
+ * [Jest Documentation](https://jestjs.io/docs/configuration#globalsetup-string)
24
+ *
25
+ * We only start the selenium server here if we are running in remote mode, which is the default.
26
+ * The mode can configured by setting the environment variable BROWSER to "chrome", "firefox" or "remote".
27
+ *
28
+ * Do not forget a global teardown function, which is called after all tests are executed,
29
+ * cf. globalTeardown.ts
30
+ */
31
+ export default async function setGlobalObjects() {
32
+ const browser = (process.env.SEFI_BROWSER as "chrome" | "firefox" | "remote") || "remote";
33
+
34
+ verbose(`Global Setup: Use browser: ${browser}`);
35
+ try {
36
+ if (browser === "remote") {
37
+ await setUpSeleniumServer();
38
+ }
39
+ } catch (err) {
40
+ console.error(`Error setting up server: ${err}`);
41
+ throw err;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Returns the path to the (cached) selenium server jar file. The jar file is automatically downloaded
47
+ * if it is not already cached.
48
+ *
49
+ * The version of the selenium server can be configured by setting the environment variable
50
+ * `SELENIUM_SERVER_VERSION`. If not set, the default version is 4.16.1.
51
+ *
52
+ * The jar file is cached in the `.cache` directory in the project root.
53
+ *
54
+ * @returns the path to the selenium server jar file
55
+ */
56
+ export async function getSeleniumServerJar() {
57
+ const seleniumServerVersion = process.env.SEFI_SELENIUM_SERVER_VERSION || "4.16.1";
58
+ const versionParts = seleniumServerVersion.split(".").map(s => parseInt(s));
59
+ if (versionParts.length !== 3) {
60
+ throw new Error(`Invalid version number: ${seleniumServerVersion}`);
61
+ }
62
+ const jarName = `selenium-server-${versionParts[0]}.${versionParts[1]}.${versionParts[2]}.jar`;
63
+ const homeDir = homedir();
64
+ const cachedPath = path.resolve(path.join(homeDir, ".cache", "selenium-server", jarName));
65
+ verbose(`Selenium server jar: ${cachedPath}`);
66
+ if (! fs.existsSync(cachedPath)) {
67
+ if (! fs.existsSync(path.dirname(cachedPath))) {
68
+ fs.mkdirSync(path.dirname(cachedPath));
69
+ }
70
+ const downloadURL = process.env.SEFI_SELENIUM_SERVER_DOWNLOAD_URL || "https://github.com/SeleniumHQ/selenium/releases/download";
71
+ const url = `${downloadURL}/selenium-${versionParts[0]}.${versionParts[1]}.0/${jarName}`;
72
+ console.log(`Download ${url} to ${cachedPath}`);
73
+ const response = await fetch(url);
74
+ if (response.status !== 200) {
75
+ throw new Error(`Error downloading ${url}: ${response.status} ${response.statusText}`);
76
+ }
77
+ const blob = await response.blob();
78
+ const buffer = Buffer.from(await blob.arrayBuffer());
79
+ writeFileSync(cachedPath, buffer);
80
+ } else {
81
+ verbose(`Selenium server jar already cached`);
82
+ }
83
+ return cachedPath;
84
+ }
85
+
86
+
87
+ async function setUpSeleniumServer() {
88
+ debug("Dynamic import of selenium-webdriver/remote")
89
+ const remote = await import("selenium-webdriver/remote");
90
+
91
+ const jarPath = await getSeleniumServerJar();
92
+
93
+ if (globalThis.server) {
94
+ await globalThis.server.kill();
95
+ verbose("`Server killed before starting again");
96
+ }
97
+
98
+ const { port} = getRemoteAddressAndPort();
99
+
100
+ debug(()=>`Start selenium server at port ${port} with SE_NODE_MAX_SESSIONS=4, SE_NODE_OVERRIDE_MAX_SESSIONS=true`);
101
+ const s = new remote.SeleniumServer(jarPath, {
102
+ port: port,
103
+ env: {
104
+ ...process.env,
105
+ "SE_NODE_MAX_SESSIONS": "4",
106
+ "SE_NODE_OVERRIDE_MAX_SESSIONS": "true",
107
+ // "JAVA_OPTS": "-XX:ActiveProcessorCount=4"
108
+ }
109
+ });
110
+
111
+ const timeout = parseInt(process.env.SEFI_SELENIUM_SERVER_START_TIMEOUT || "5000");
112
+ const actualAddress = await s.start(timeout);
113
+ globalThis.server = s;
114
+ // const address = await globalThis.server.address()
115
+ debug(()=>`Selenium server started at: ${actualAddress}`);
116
+ }
@@ -0,0 +1,22 @@
1
+
2
+ /**
3
+ * This function is called by Jest once after tests have been executed.
4
+ * In order to get called, it must be referenced in the jest configuration file:
5
+ * ```
6
+ * "globalTeardown": "<rootDir>/tests/globalTeardown.ts"
7
+ * ```
8
+ * **Important Note**: "Any global variables that are defined through globalSetup can only be
9
+ * read in globalTeardown. You cannot retrieve globals defined here in your test suites."
10
+ * [Jest Documentation](https://jestjs.io/docs/configuration#globalsetup-string)
11
+ *
12
+ * Here, we stop the remote selenium server if it was started in globalSetup.ts.
13
+ */
14
+ export default async function killServer() {
15
+ if (globalThis.server) {
16
+ await server.kill();
17
+ console.log(`global Teardown: Server killed`);
18
+ } else {
19
+ console.log(`global Teardown: No server to kill`);
20
+ }
21
+ globalThis.server = undefined;
22
+ }
@@ -0,0 +1,128 @@
1
+ import dotenv from "dotenv";
2
+ dotenv.config() // read ".env"
3
+
4
+ import { Builder, Capabilities, WebDriver } from "selenium-webdriver";
5
+ import { getRemoteAddressAndPort, setBrowserWindowSize, setViewPortSize } from "./seUtils";
6
+ import { debug, verbose } from "./log";
7
+
8
+ export const LOCAL_SETUP_TIMEOUT = 20000;
9
+
10
+ export async function localSetup(page: string, width = 500, height = 500, acceptUntrustedCertificates = true): Promise<WebDriver> {
11
+ debug(() => `localSetup("${page}", ${width}, ${height}, ${acceptUntrustedCertificates}), getWebDriver ...`);
12
+ const driver = await getWebDriver(acceptUntrustedCertificates);
13
+ verbose(() => `Driver started, load page: ${page}`);
14
+ await driver.get(page);
15
+ debug(() => `Page loaded, set browser window size: ${width}x${height}`);
16
+ await setBrowserWindowSize(driver, width, height);
17
+ debug("Page loaded and window size set, local setup done, return driver.")
18
+ return driver;
19
+ }
20
+
21
+ /**
22
+ * To be configured in jest setup file via globalSetup as it needs to be run before any tests in all files.
23
+ * Additionally,
24
+ * ```
25
+ * await driver.get(page);
26
+ * await setViewPortSize(500, 500);
27
+ * ```
28
+ * are to be called.
29
+ */
30
+ async function getWebDriver(acceptUntrustedCertificates: boolean) {
31
+ const browser = (process.env.SEFI_BROWSER as "chrome" | "firefox" | "remote") || "remote";
32
+
33
+ debug(() => `Use browser: ${browser}`);
34
+ try {
35
+ if (browser === "chrome") {
36
+ return await setUpWebDriverChrome(acceptUntrustedCertificates);
37
+ } else if (browser === "firefox") {
38
+ return await setUpWebDriverFirefox(acceptUntrustedCertificates);
39
+ } else if (browser === "remote") {
40
+ return await setUpWebDriverInDocker(acceptUntrustedCertificates);
41
+ } else {
42
+ throw new Error(`Unknown browser: ${browser}`);
43
+ }
44
+ } catch (err) {
45
+ console.error(`Error setting up driver: ${err}`);
46
+ throw err;
47
+ }
48
+ }
49
+
50
+
51
+ async function setUpWebDriverInDocker(acceptUntrustedCertificates: boolean) {
52
+ debug("Dynamic import of selenium-webdriver/firefox");
53
+ debug("Remote browser, load firefox webdriver");
54
+ const firefox = await import('selenium-webdriver/firefox');
55
+
56
+ const { address } = getRemoteAddressAndPort();
57
+ const start = Date.now();
58
+
59
+ const options = new firefox.Options().addArguments("-headless");
60
+ options.setAcceptInsecureCerts(acceptUntrustedCertificates);
61
+
62
+ debug(() => `Start headless firefox, remote server at ${address}`)
63
+ const driver = await new Builder()
64
+ .usingServer(address)
65
+ .withCapabilities(Capabilities.firefox())
66
+ .setFirefoxOptions(options)
67
+ .build();
68
+
69
+ debug(() => {
70
+ const duration = (Date.now() - start) / 1000;
71
+ return `Driver resolved and connected, required ${duration}s.`
72
+ });
73
+ return driver;
74
+ }
75
+
76
+
77
+ /**
78
+ * To be called in the beforeAll() function of a test suite.
79
+ * Additionally,
80
+ * ```
81
+ * await driver.get(page);
82
+ * await setViewPortSize(500, 500);
83
+ * ```
84
+ * are to be called.
85
+ */
86
+ async function setUpWebDriverChrome(acceptUntrustedCertificates: boolean) {
87
+ debug("Dynamic import of chromedriver and selenium-webdriver/chrome");
88
+ //@ts-ignore
89
+ await import('chromedriver');
90
+ const chrome = await import('selenium-webdriver/chrome');
91
+
92
+ const options = new chrome.Options().addArguments("--headless=new");
93
+ options.setAcceptInsecureCerts(acceptUntrustedCertificates);
94
+
95
+ debug(() => `Start headless chrome`)
96
+ const driver = await new Builder()
97
+ .forBrowser('chrome')
98
+ .setChromeOptions(options)
99
+ .build();
100
+ return driver;
101
+ }
102
+
103
+ /**
104
+ * To be called in the beforeAll() function of a test suite.
105
+ * Additionally,
106
+ * ```
107
+ * await driver.get(page);
108
+ * await setViewPortSize(500, 500);
109
+ * ```
110
+ * are to be called.
111
+ */
112
+ async function setUpWebDriverFirefox(acceptUntrustedCertificates: boolean) {
113
+ debug("Dynamic import of geckodriver and selenium-webdriver/firefox");
114
+ //@ts-ignore
115
+ await import('geckodriver'); // installed geckodriver will cause remote configuration to fail in docker...
116
+ const firefox = await import('selenium-webdriver/firefox');
117
+
118
+ const options = new firefox.Options().addArguments("-headless");
119
+ options.setAcceptInsecureCerts(acceptUntrustedCertificates);
120
+
121
+ debug(() => `Start headless firefox`)
122
+ const driver = await new Builder()
123
+ .forBrowser('firefox')
124
+ .setFirefoxOptions(options)
125
+ .build();
126
+
127
+ return driver;
128
+ }
@@ -0,0 +1,14 @@
1
+ import { WebDriver } from "selenium-webdriver";
2
+
3
+ /**
4
+ * To be called in the afterAll() function of a test suite.
5
+ */
6
+ export async function localTeardown(driver: WebDriver) {
7
+ if (driver) {
8
+ await driver.quit();
9
+ // console.log(`Driver quit`);
10
+ } else {
11
+ throw new Error("Driver not started yet");
12
+
13
+ }
14
+ }
package/src/log.ts ADDED
@@ -0,0 +1,19 @@
1
+ const logLevel = (process.env.SEFI_LOG_LEVEL || "info").toLowerCase();
2
+
3
+ export function verbose(msg: any) {
4
+ if (logLevel=="debug" || logLevel=="verbose") {
5
+ if (msg instanceof Function) {
6
+ msg = msg();
7
+ }
8
+ console.log(msg);
9
+ }
10
+ }
11
+
12
+ export function debug(msg: any) {
13
+ if (logLevel=="debug") {
14
+ if (msg instanceof Function) {
15
+ msg = msg();
16
+ }
17
+ console.log(msg);
18
+ }
19
+ }