Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/faker #325

Merged
merged 6 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,4 @@ Spectrum leverages these projects you should definitely check out!
* [VicTools JsonSchema Generator](https://victools.github.io/jsonschema-generator/#introduction)
* [Jekyll](https://jekyllrb.com/)
* [Modernist Theme](https://github.com/pages-themes/modernist)
* [Datafaker](https://github.com/datafaker-net/datafaker)
42 changes: 42 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,48 @@ The `Data` generic must be specified only in classes actually using it. There's

---

# Datafaker

[Datafaker](https://www.datafaker.net/documentation/getting-started/){:target="_blank"}
is a popular library to generate fake data. You can leverage the injected `faker` instance in both pages and tests:

{% include copyCode.html %}

```java

@Test
void test() {
String name = faker.name().fullName(); // Miss Samanta Schmidt
String number = faker.numerify("##");
String anotherNumber = faker.expression("#{numerify '##'}");
...
}
```

You can provide a custom random seed and a locale in your `configuration*.yaml`, for example:

{% include copyCode.html %}

```yaml
faker:
locale: it
random: 24
```

The default in the internal
[configuration.default.yaml]({{ site.repository_url }}/spectrum/src/main/resources/yaml/configuration.default.yaml){:target="_blank"}
is the following, meaning if you need no random seed and the english locale you can avoid configuring it explicitly:

{% include copyCode.html %}

```yaml
faker:
locale: en
random: null
```

---

# Event Sourcing - Notifications

Spectrum leverages event sourcing, meaning throughout the execution it fires events at specific moments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ public LoginPage waitForPageLoading() {
}

public LoginPage loginWith(final Data.User user) {
clearAndSendKeys(username, user.getName());
clearAndSendKeys(password, user.getPassword());
return loginWith(user.getName(), user.getPassword());
}

public LoginPage loginWith(final String name, final String pwd) {
clearAndSendKeys(username, name);
clearAndSendKeys(password, pwd);

form.submit();
return this;
Expand Down
86 changes: 86 additions & 0 deletions it/src/test/java/io/github/giulong/spectrum/it/tests/FakerIT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.github.giulong.spectrum.it.tests;

import io.github.giulong.spectrum.it.pages.InputsPage;
import io.github.giulong.spectrum.it.pages.LoginPage;
import net.datafaker.providers.base.Name;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import java.util.Objects;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.openqa.selenium.Keys.ARROW_UP;
import static org.openqa.selenium.support.ui.ExpectedConditions.urlContains;

@DisplayName("Faker")
@SuppressWarnings("unused")
public class FakerIT extends BaseIT {

private LoginPage loginPage;
private InputsPage inputsPage;

@Test
@DisplayName("the login should fail leveraging random name generated by Faker")
void login() {
final Name name = faker.name();
loginPage
.open()
.loginWith(name.firstName(), name.lastName());

pageLoadWait.until(urlContains("/login"));

assertTrue(isPresent(By.id("flash")));
assertFalse(Objects.requireNonNull(driver.getCurrentUrl()).endsWith("/secure"));
}

@DisplayName("expressions should generate random numbers")
@ParameterizedTest(name = "with {0} increments")
@ValueSource(ints = {0, 2, 5})
void inputs(final int increments) {
final String number = faker.expression("#{numerify '##'}");

inputsPage.open();

final WebElement input = inputsPage.getInput();
input.sendKeys(number);

for (int i = 0; i < increments; i++) {
input.sendKeys(ARROW_UP);
}

final int actual = Integer.parseInt(Objects.requireNonNull(input.getDomProperty("value")));
final int expected = Integer.parseInt(number) + increments;
assertEquals(expected, actual);
}

@DisplayName("expressions should generate random numbers from a values provider method, to prove the Faker instance is static")
@ParameterizedTest(name = "with numerify expression number: {0}")
@MethodSource("valuesProvider")
void inputsWithProvider(final String number) {
inputsPage.open();

final WebElement input = inputsPage.getInput();
input.sendKeys(number);
input.sendKeys(ARROW_UP);
input.sendKeys(ARROW_UP);

final int actual = Integer.parseInt(Objects.requireNonNull(input.getDomProperty("value")));
final int expected = Integer.parseInt(number) + 2;
assertEquals(expected, actual);
}

static Stream<Arguments> valuesProvider() {
return Stream.of(
arguments(faker.numerify("##")),
arguments(faker.numerify("###"))
);
}
}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@
<version>0.2.5</version>
</dependency>

<dependency>
<groupId>net.datafaker</groupId>
<artifactId>datafaker</artifactId>
<version>2.4.2</version>
</dependency>

<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions spectrum/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@
<artifactId>jcodec-javase</artifactId>
</dependency>

<dependency>
<groupId>net.datafaker</groupId>
<artifactId>datafaker</artifactId>
</dependency>

<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.Media;
import net.datafaker.Faker;
import io.github.giulong.spectrum.interfaces.Shared;
import io.github.giulong.spectrum.types.TestData;
import io.github.giulong.spectrum.utils.Configuration;
Expand Down Expand Up @@ -49,6 +50,9 @@ public abstract class SpectrumEntity<T extends SpectrumEntity<T, Data>, Data> {
@Shared
protected static ExtentReports extentReports;

@Shared
protected static Faker faker;

@Shared
protected ExtentTest extentTest;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.github.giulong.spectrum.utils.js.JsWebElementProxyBuilder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.datafaker.Faker;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand Down Expand Up @@ -69,6 +70,9 @@ public abstract class SpectrumTest<Data> extends SpectrumEntity<SpectrumTest<Dat
@RegisterExtension
public static final JsResolver JS_RESOLVER = new JsResolver();

@RegisterExtension
public static final FakerResolver FAKER_RESOLVER = new FakerResolver();

@RegisterExtension
public static final SpectrumInterceptor SPECTRUM_INTERCEPTOR = new SpectrumInterceptor();

Expand All @@ -81,10 +85,11 @@ public abstract class SpectrumTest<Data> extends SpectrumEntity<SpectrumTest<Dat
private final YamlUtils yamlUtils = YamlUtils.getInstance();

@BeforeAll
static void beforeAll(final Configuration configuration, final EventsDispatcher eventsDispatcher, final ExtentReports extentReports) {
static void beforeAll(final Configuration configuration, final EventsDispatcher eventsDispatcher, final ExtentReports extentReports, final Faker faker) {
SpectrumTest.configuration = configuration;
SpectrumTest.eventsDispatcher = eventsDispatcher;
SpectrumTest.extentReports = extentReports;
SpectrumTest.faker = faker;
}

@BeforeEach
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.github.giulong.spectrum.extensions.resolvers;

import io.github.giulong.spectrum.utils.Configuration;
import net.datafaker.Faker;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver;

import static io.github.giulong.spectrum.extensions.resolvers.ConfigurationResolver.CONFIGURATION;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

@Slf4j
public class FakerResolver extends TypeBasedParameterResolver<Faker> {

public static final String FAKER = "faker";

@Override
public Faker resolveParameter(final ParameterContext arg0, final ExtensionContext context) throws ParameterResolutionException {
final ExtensionContext.Store rootStore = context.getRoot().getStore(GLOBAL);

return rootStore.getOrComputeIfAbsent(FAKER, e -> {
log.debug("Resolving {}", FAKER);
final Configuration.Faker faker = rootStore.get(CONFIGURATION, Configuration.class).getFaker();

return new Faker(faker.getLocale(), faker.getRandom());
}, Faker.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.giulong.spectrum.internals.jackson.deserializers;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Random;

import static lombok.AccessLevel.PRIVATE;

@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class RandomDeserializer extends JsonDeserializer<Random> {

private static final RandomDeserializer INSTANCE = new RandomDeserializer();

public static RandomDeserializer getInstance() {
return INSTANCE;
}

@Override
public Random deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
final long value = jsonParser.getValueAsLong();
log.trace("Deserializing random from value {}", value);

return new Random(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@
import java.io.File;
import java.net.URL;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import java.util.logging.Level;

import static lombok.AccessLevel.PRIVATE;
Expand Down Expand Up @@ -72,6 +69,9 @@ public class Configuration {
@JsonPropertyDescription("FreeMarker template engine configuration. See https://freemarker.apache.org/")
private FreeMarker freeMarker;

@JsonPropertyDescription("Datafaker configuration. See https://www.datafaker.net/documentation/getting-started/")
private Faker faker;

@JsonPropertyDescription("Events consumers, such as those to send email notifications, for example")
private List<EventsConsumer> eventsConsumers;

Expand Down Expand Up @@ -620,4 +620,17 @@ public static class FreeMarker {
@JsonPropertyDescription("Number format to be used. See https://freemarker.apache.org/docs/ref_directive_setting.html")
private String numberFormat;
}

@Getter
@Generated
public static class Faker {

@JsonSchemaTypes(String.class)
@JsonPropertyDescription("Locale to be used. See https://www.datafaker.net/documentation/getting-started/")
private Locale locale;

@JsonSchemaTypes(int.class)
@JsonPropertyDescription("Random seed to be used. See https://www.datafaker.net/documentation/getting-started/")
private Random random;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.util.Random;

import static com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
import static lombok.AccessLevel.PRIVATE;
Expand Down Expand Up @@ -50,6 +51,7 @@ public final class YamlUtils {
buildModuleFor(Driver.class, DriverDeserializer.getInstance()),
buildModuleFor(Environment.class, EnvironmentDeserializer.getInstance()),
buildModuleFor(Class.class, ClassDeserializer.getInstance()),
buildModuleFor(Random.class, RandomDeserializer.getInstance()),
buildDynamicModuleFor(LogTestBookReporter.class, "yaml/dynamic/testbook/logReporter.yaml"),
buildDynamicModuleFor(TxtTestBookReporter.class, "yaml/dynamic/testbook/txtReporter.yaml"),
buildDynamicModuleFor(HtmlTestBookReporter.class, "yaml/dynamic/testbook/htmlReporter.yaml"),
Expand Down
4 changes: 4 additions & 0 deletions spectrum/src/main/resources/yaml/configuration.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ freeMarker:
locale: US
numberFormat: 0.##;; roundingMode=halfUp

faker:
locale: en
random: null

# Internal events consumers. Cannot be removed, modified, nor added from client side configuration*.yaml, as lists' elements are appended when merging yaml files. You can add yours
eventsConsumers:
- extentTest: # We need to add an entry to the Extent Report once each test is done
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ void getSharedFields() {
.toList();

// we're checking real size and names here, no mocks
assertEquals(16, actual.size());
assertEquals(17, actual.size());
assertTrue(sharedFieldsNames.containsAll(List.of(
"configuration",
"extentReports",
Expand All @@ -139,6 +139,7 @@ void getSharedFields() {
"scriptWait",
"downloadWait",
"js",
"faker",
"data",
"statefulExtentTest",
"testContext",
Expand Down
Loading
Loading