Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
06d61ff
build: versioning framework-only build with tests run configuration
giulong Dec 2, 2025
bb0c862
feat: introducing external interpolation to resolve system properties…
giulong Dec 2, 2025
b2d4156
fix(external interpolation): ConfigurationInterpolator returns an emp…
giulong Dec 3, 2025
0fde701
test: re-enabling and cleaning InterpolatedStringDeserializerTest
giulong Dec 3, 2025
bc606bf
refactor: aligning profile id in it-windows module to others
giulong Dec 5, 2025
490464f
fix(external interpolation): config node is read from the base config…
giulong Dec 5, 2025
b89cb69
test(external interpolation): moving a couple configuration keys in s…
giulong Dec 6, 2025
cf5c805
build(external interpolation): setting spectrum.video.skipDuplicateFr…
giulong Dec 6, 2025
b92af67
refactor: cleaning interpolated deserializers
giulong Dec 7, 2025
60df731
fix(external interpolation): exporting env variables in bash process …
giulong Dec 7, 2025
86d9d47
feat(external interpolation): introducing interpolators priority to o…
giulong Dec 7, 2025
e44ba95
refactor(external interpolation): short circuiting on first interpola…
giulong Dec 7, 2025
16f6967
build(deps): bump selenium.version from 4.38.0 to 4.39.0
dependabot[bot] Dec 8, 2025
bbc0d6b
Merge pull request #502 from giulong/dependabot/maven/develop/seleniu…
giulong Dec 8, 2025
e96ebeb
refactor(external interpolation): renaming ConfigurationInterpolator …
giulong Dec 8, 2025
df89359
docs(external interpolation): adding the new section in gh pages
giulong Dec 8, 2025
f7644e4
feat(external interpolation): introducing transformCase property for …
giulong Dec 8, 2025
8384970
docs(external interpolation): adding transformCase property descripti…
giulong Dec 8, 2025
96c09a2
build: adding paths-ignore to codeql gh action
giulong Dec 8, 2025
04e9577
refactor: minor InPlaceInterpolator code cleaning, removing a redunda…
giulong Dec 8, 2025
81998a6
Merge pull request #503 from giulong/feature/external-interpolation
giulong Dec 8, 2025
ff6491e
docs: minor change
giulong Dec 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ jobs:
- name: Run browsers ITs
shell: bash
run: |
export SPECTRUM_VIDEO_SKIPDUPLICATEFRAMES=false
export SPECTRUM_APPLICATION_BASEURL='https://the-internet.herokuapp.com/'
./mvnw install:install-file -ntp -Dfile=$GITHUB_WORKSPACE/spectrum-${{ env.VERSION }}.jar -DgroupId=io.github.giulong -DartifactId=spectrum -Dversion=${{ env.VERSION }} -Dpackaging=jar
./mvnw install -DskipSign -Dmaven.plugin.validation=NONE ${{ matrix.directives }} -ntp -P ${{ matrix.profiles }}

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: CodeQL
on:
push:
branches: [ "develop", "feature/**", "bugfix/**" ]
paths-ignore: [ '.mvn/**', 'mvnw', 'mvnw.cmd', '.run/**', 'docs/**', '.editorconfig', '.gitignore', '**.md' ]

jobs:
analyze:
Expand Down
37 changes: 37 additions & 0 deletions .run/spectrum framework-only no tests.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spectrum framework-only no tests" type="MavenRunConfiguration" factoryName="Maven">
<MavenSettings>
<option name="myGeneralSettings" />
<option name="myRunnerSettings" />
<option name="myRunnerParameters">
<MavenRunnerParameters>
<option name="cmdOptions" />
<option name="profiles">
<set />
</option>
<option name="goals">
<list>
<option value="install" />
<option value="-DskipTests" />
<option value="-DskipSign" />
<option value="-ntp" />
<option value="-P" />
<option value="framework-only" />
</list>
</option>
<option name="multimoduleDir" />
<option name="pomFileName" />
<option name="profilesMap">
<map />
</option>
<option name="projectsCmdOptionValues">
<list />
</option>
<option name="resolveToWorkspace" value="false" />
<option name="workingDirPath" value="$PROJECT_DIR$" />
</MavenRunnerParameters>
</option>
</MavenSettings>
<method v="2" />
</configuration>
</component>
1 change: 0 additions & 1 deletion .run/spectrum framework-only.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<option name="goals">
<list>
<option value="install" />
<option value="-DskipTests" />
<option value="-DskipSign" />
<option value="-ntp" />
<option value="-P" />
Expand Down
180 changes: 177 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,55 @@ runtime:

## Values interpolation

Plain values (not objects nor arrays) in `configuration*.yaml` and `data*.yaml` can be interpolated with a dollar-string
in one of these two ways, depending on the type needed as result. Let's suppose we have the variable `key = 123`:
Plain values (not objects nor arrays) in `configuration*.yaml` and `data*.yaml` can be interpolated, meaning their value can be injected
instead of being hardcoded. This is quite useful if you want to keep the same configuration files, while being able to tweak it at runtime, based
on the execution environment for instance.

There are a few ways to interpolate configuration keys:

* [In-place interpolation](#in-place-interpolation): directly in the `configuration.yaml`
* [Environment variables interpolation](#environment-variables-interpolation): injecting values from the current env
* [System properties interpolation](#system-properties-interpolation): injecting values passed as system properties

> 💡 **Tip**<br/>
> You can mix all the available options, for example having some keys interpolated in-place, and some others injected from env vars and system properties.

Each interpolator can be configured in the base `configuration.yaml` under the `config` node. Here you can see the internal
[configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:target="_blank"}:

{% include copyCode.html %}

```yaml
# Generic configuration. This node is read only from the base configuration.yaml
config:
interpolators: # Configuration keys interpolators
environment: # Environment variables interpolator
priority: 0 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
prefix: spectrum # Variable prefix
delimiter: . # Variable tokens' delimiter
transformCase: none # Function to specify how to transform the original camelCase of the key to match the external variable to search
properties: # Properties interpolator
priority: 1 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
prefix: spectrum # Variable prefix
delimiter: . # Variable tokens' delimiter
transformCase: none # Function to specify how to transform the original camelCase of the key to match the external variable to search
inPlace: # In-place configuration file interpolator
priority: 2 # Sets the order of evaluation of this interpolator among others. Higher priority wins.
enabled: true
```

You can see that each interpolator has a `priority`. That's an int value that specifies the resolution order:
when a key is resolved by more than one interpolator, the one with the highest priority is used to inject the interpolated value.
The other keys are explained in the corresponding sections.

> ⚠️ **Overriding the `config` node**<br/>
> You can customize the `config` node in the base `configuration.yaml` only. Overriding it in any other `configuration-<PROFILE>.yaml`
> simply won't have any effect. This is needed since the `config` node is a *meta*-configuration that specifies how to read all the other keys.

### In-place Interpolation

You can interpolate values directly in the `configuration*.yaml` and `data*.yaml` with a dollar-string in one of the following two ways,
depending on the type needed as result. Let's suppose we have the variable `key = 123`:

| Needed type | Interpolation key | Result | Behaviour if not found |
|-------------|-------------------|--------|--------------------------------------------------------------|
Expand Down Expand Up @@ -746,18 +793,145 @@ runtime:
profiles: ${active-profiles:-local}
```

These are the variables already available in the [configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:
These variables are already available in the [configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:
target="_blank"}.
You can add your own and even override the default ones in your `configuration*.yaml`:

| Variable | Default Windows | Default *nix |
|----------------------|------------------------------|------------------------------|
| spectrum.profiles | local | local |
| spectrum.driver | chrome | chrome |
| spectrum.environment | local | local |
| downloadsFolder | ${user.dir}\target\downloads | ${user.dir}/target/downloads |
| summaryReportOutput | target/spectrum/summary | target/spectrum/summary |
| testBookReportOutput | target/spectrum/testbook | target/spectrum/testbook |

The in-place interpolator is enabled by default with the highest priority. You can disable it or change its priority with this configuration snippet:

{% include copyCode.html %}

```yaml
config:
interpolators:
inPlace:
priority: 123
enabled: false
```

### Environment Variables Interpolation

You can **avoid specifying keys directly in the yaml files** and interpolate values taken from environment variables
by providing this configuration snippet:

{% include copyCode.html %}

```yaml
config:
interpolators:
environment: { }
```

Providing an empty object as above, you'll leverage on the internal defaults, which are the following:

{% include copyCode.html %}

```yaml
config:
interpolators:
environment:
priority: 0
prefix: spectrum
delimiter: .
transformCase: none
```

This means every configuration key will be searched in env vars, with the `spectrum` prefix and words delimited by a dot.
For instance, you can inject the `application.baseUrl` setting an env variable named `spectrum.application.baseUrl`.

To give you another example, you can use this config to inject env vars like `APPLICATION_BASEURL`:

{% include copyCode.html %}

```yaml
config:
interpolators:
environment:
prefix: ''
delimiter: _
transformCase: upper
```

Allowed values for the `transformCase` property are:

| Value | Description | Example |
|---------|-----------------------------------------------------------------------------------|---------------------|
| `none` | searches a key with the same case of the property, which is `camelCase` (default) | application.baseUrl |
| `lower` | searches a lowercase key | application.baseurl |
| `upper` | searches a uppercase key | APPLICATION.BASEURL |

> ⚠️ **Priority**<br/>
> Pay attention to the priority: by default, the `inPlace` interpolator takes precedence over this one.
> This means that if you provide the same key in the yaml file, the env var will be ignored. You have to options to inject the env var:
> * delete the hardcoded key
> * set a higher priority in the environment interpolator

### System Properties Interpolation

You can **avoid specifying keys directly in the yaml files** and interpolate values taken from system properties
by providing this configuration snippet:

{% include copyCode.html %}

```yaml
config:
interpolators:
properties: { }
```

Providing an empty object as above, you'll leverage on the internal defaults, which are the following:

{% include copyCode.html %}

```yaml
config:
interpolators:
properties:
priority: 1
prefix: spectrum
delimiter: .
transformCase: none
```

This means every configuration key will be searched in system properties, with the `spectrum` prefix and words delimited by a dot.
For instance, you can inject the `application.baseUrl` setting the system property `-Dspectrum.application.baseUrl`.

To give you another example, you can use this config to inject system properties like `-DAPPLICATION_BASEURL`:

{% include copyCode.html %}

```yaml
config:
interpolators:
properties:
prefix: ''
delimiter: _
transformCase: upper
```

Allowed values for the `transformCase` property are:

| Value | Description | Example |
|---------|-----------------------------------------------------------------------------------|---------------------|
| `none` | searches a key with the same case of the property, which is `camelCase` (default) | application.baseUrl |
| `lower` | searches a lowercase key | application.baseurl |
| `upper` | searches a uppercase key | APPLICATION.BASEURL |

> ⚠️ **Priority**<br/>
> Pay attention to the priority: by default, the `inPlace` interpolator takes precedence over this one.
> This means that if you provide the same key in the yaml file, the system property will be ignored. You have to options to inject the system property:
> * delete the hardcoded key
> * set a higher priority in the properties interpolator

---

## Configuring the Driver
Expand Down
2 changes: 1 addition & 1 deletion it-windows/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

<profiles>
<profile>
<id>windows</id>
<id>chrome</id>
<activation>
<property>
<name>windowsTests</name>
Expand Down
9 changes: 9 additions & 0 deletions it/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<environmentVariables>
<SPECTRUM_VIDEO_SKIPDUPLICATEFRAMES>false</SPECTRUM_VIDEO_SKIPDUPLICATEFRAMES>
<SPECTRUM_APPLICATION_BASEURL>https://the-internet.herokuapp.com/</SPECTRUM_APPLICATION_BASEURL>
</environmentVariables>
<systemPropertyVariables>
<spectrum.application.baseUrl>overridden by the env variable above</spectrum.application.baseUrl>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Expand Down
12 changes: 10 additions & 2 deletions it/src/test/resources/configuration.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
config:
interpolators:
environment:
priority: 10
delimiter: _
transformCase: upper
properties: { }

application:
baseUrl: https://the-internet.herokuapp.com/
baseUrl: set as env var in pom.xml. Environment interpolator has a higher priority (see above)

drivers:
waits:
Expand All @@ -18,7 +26,7 @@ video:
frames:
- autoAfter
- manual
skipDuplicateFrames: false
# skipDuplicateFrames: false set as env variable in pom.xml

extent:
theme: DARK
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<simplejavamail.version>8.12.6</simplejavamail.version>
<jsonschemagenerator.version>4.38.0</jsonschemagenerator.version>
<slf4j.version>2.0.17</slf4j.version>
<selenium.version>4.38.0</selenium.version>
<selenium.version>4.39.0</selenium.version>
</properties>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import io.github.giulong.spectrum.types.ProjectProperties;
import io.github.giulong.spectrum.utils.*;
import io.github.giulong.spectrum.utils.Configuration.Config;
import io.github.giulong.spectrum.utils.events.EventsDispatcher;

import lombok.extern.slf4j.Slf4j;
Expand All @@ -21,6 +22,7 @@ public class SpectrumSessionListener implements LauncherSessionListener {
public static final String DEFAULT_CONFIGURATION_UNIX_YAML = "yaml/configuration.default.unix.yaml";
public static final String CONFIGURATION = "configuration";
public static final String PROFILE_NODE = "/runtime/profiles";
public static final String CONFIG_NODE = "/config";
public static final String VARS_NODE = "/vars";

private final Vars vars = Vars.getInstance();
Expand All @@ -41,6 +43,7 @@ public void launcherSessionOpened(final LauncherSession session) {
final ProjectProperties projectProperties = yamlUtils.readInternal("properties.yaml", ProjectProperties.class);
log.info(freeMarkerWrapper.interpolate(fileUtils.read("banner.txt"), projectProperties));

parseConfig();
parseConfiguration();
session.getLauncher().registerTestExecutionListeners(configuration.getSummary().getSummaryGeneratingListener());

Expand All @@ -64,13 +67,25 @@ public void launcherSessionClosed(final LauncherSession session) {
eventsDispatcher.sessionClosed();
}

void parseConfig() {
final Config config = yamlUtils.readInternalNode(CONFIG_NODE, DEFAULT_CONFIGURATION_YAML);

if (isUnix()) {
yamlUtils.updateWithInternalNode(config, CONFIG_NODE, DEFAULT_CONFIGURATION_UNIX_YAML);
}

yamlUtils.updateWithClientNode(config, CONFIG_NODE, CONFIGURATION);
configuration.setConfig(config);
}

void parseConfiguration() {
final List<String> profileConfigurations = parseProfiles()
.stream()
.map(profile -> String.format("configuration-%s", profile))
.toList();

parseVars(profileConfigurations);

yamlUtils.updateWithInternalFile(configuration, DEFAULT_CONFIGURATION_YAML);

if (isUnix()) {
Expand Down
Loading
Loading