diff --git a/bb-eyes/pom.xml b/bb-eyes/pom.xml
new file mode 100644
index 00000000..3119baa1
--- /dev/null
+++ b/bb-eyes/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+ 4.0.0
+
+
+ bobcat
+ com.cognifide.qa.bb
+ 1.5.1-SNAPSHOT
+
+
+ bb-eyes
+ Bobcat Eyes
+
+
+
+ com.google.inject
+ guice
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ junit
+ junit
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ com.applitools
+ eyes-selenium-java3
+ RELEASE
+
+
+ io.appium
+ java-client
+
+
+
+
+
+ com.cognifide.qa.bb
+ bb-core
+ ${project.version}
+
+
+ com.cognifide.qa.bb
+ bb-junit5
+ ${project.version}
+
+
+
+
+
+
+ org.apache.rat
+ apache-rat-plugin
+
+
+
+
+
diff --git a/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/guice/EyesModule.java b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/guice/EyesModule.java
new file mode 100644
index 00000000..d013b7ed
--- /dev/null
+++ b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/guice/EyesModule.java
@@ -0,0 +1,34 @@
+/*-
+ * #%L
+ * Bobcat
+ * %%
+ * Copyright (C) 2018 Cognifide Ltd.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.cognifide.qa.bb.eyes.guice;
+
+import com.applitools.eyes.selenium.Eyes;
+import com.google.inject.AbstractModule;
+
+/**
+ * Eyes Guice module. Provides an {@link Eyes} instance.
+ */
+public class EyesModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(Eyes.class).toProvider(EyesProvider.class);
+ }
+}
diff --git a/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/guice/EyesProvider.java b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/guice/EyesProvider.java
new file mode 100644
index 00000000..897bb01d
--- /dev/null
+++ b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/guice/EyesProvider.java
@@ -0,0 +1,65 @@
+/*-
+ * #%L
+ * Bobcat
+ * %%
+ * Copyright (C) 2018 Cognifide Ltd.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.cognifide.qa.bb.eyes.guice;
+
+import com.applitools.eyes.selenium.Eyes;
+import com.cognifide.qa.bb.guice.ThreadScoped;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.name.Named;
+
+/**
+ * Provides a thread-scoped {@link Eyes} instance. Bound in {@link EyesModule}.
+ *
+ * Requires {@code eyes.apiKey} property.
+ *
+ * Additional configurable options:
+ *
+ * - {@code eyes.fullPageScreenshots} - defines if full-page screenshots should be captured
+ *
+ */
+@ThreadScoped
+public class EyesProvider implements Provider {
+
+ private Eyes cachedEyes;
+
+ @Inject
+ @Named("eyes.apiKey")
+ private String apiKey;
+
+ @Inject
+ @Named("eyes.fullPageScreenshots")
+ private boolean fullPageScreenshots;
+
+ @Override
+ public Eyes get() {
+ if (cachedEyes == null) {
+ cachedEyes = create();
+ }
+ return cachedEyes;
+ }
+
+ private Eyes create() {
+ Eyes eyes = new Eyes();
+ eyes.setApiKey(apiKey);
+ eyes.setForceFullPageScreenshot(fullPageScreenshots);
+ return eyes;
+ }
+}
diff --git a/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit4/WithEyes.java b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit4/WithEyes.java
new file mode 100644
index 00000000..aae80496
--- /dev/null
+++ b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit4/WithEyes.java
@@ -0,0 +1,102 @@
+/*-
+ * #%L
+ * Bobcat
+ * %%
+ * Copyright (C) 2018 Cognifide Ltd.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.cognifide.qa.bb.eyes.junit4;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.events.EventFiringWebDriver;
+
+import com.applitools.eyes.selenium.Eyes;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+/**
+ * JUnit 4 {@link TestRule} that enables Eyes integration.
+ *
+ * Example usage:
+ *
+ * public class ExampleTest {
+ * @Rule
+ * @Inject
+ * public WithEyes withEyes;
+ *
+ * public void test() {
+ * // test actions
+ * withEyes.getEyes().checkWindow("Example checkpoint");
+ * }
+ * }
+ *
+ */
+public class WithEyes implements TestRule {
+
+ private final WebDriver webDriver;
+ private final Eyes eyes;
+ private String appName;
+
+ @Inject
+ public WithEyes(WebDriver webDriver, Eyes eyes, @Named("eyes.appName") String appName) {
+ this.webDriver = webDriver;
+ this.eyes = eyes;
+ this.appName = appName;
+ }
+
+ /**
+ * @return the avaialable WebDriver instance
+ */
+ public WebDriver getWebDriver() {
+ return webDriver;
+ }
+
+ /**
+ * @return the configured and initialized {@link Eyes} instance.
+ */
+ public Eyes getEyes() {
+ return eyes;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ WebDriver wrappedDriver = ((EventFiringWebDriver) webDriver).getWrappedDriver();
+ eyes.open(wrappedDriver, appName, description.getMethodName());
+ base.evaluate();
+ } finally {
+ closeEyes();
+ }
+ }
+ };
+ }
+
+ private void closeEyes() {
+ try {
+ eyes.close();
+ } finally {
+ eyes.abortIfNotClosed();
+ }
+ }
+}
diff --git a/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit5/WithEyes.java b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit5/WithEyes.java
new file mode 100644
index 00000000..b898bab4
--- /dev/null
+++ b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit5/WithEyes.java
@@ -0,0 +1,36 @@
+/*-
+ * #%L
+ * Bobcat
+ * %%
+ * Copyright (C) 2018 Cognifide Ltd.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.cognifide.qa.bb.eyes.junit5;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Meta-annotation enabling the {@link WithEyesExtension}.
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith(WithEyesExtension.class)
+public @interface WithEyes {
+}
diff --git a/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit5/WithEyesExtension.java b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit5/WithEyesExtension.java
new file mode 100644
index 00000000..bb0fe7fa
--- /dev/null
+++ b/bb-eyes/src/main/java/com/cognifide/qa/bb/eyes/junit5/WithEyesExtension.java
@@ -0,0 +1,73 @@
+/*-
+ * #%L
+ * Bobcat
+ * %%
+ * Copyright (C) 2018 Cognifide Ltd.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package com.cognifide.qa.bb.eyes.junit5;
+
+import static com.cognifide.qa.bb.junit5.JUnit5Constants.NAMESPACE;
+
+import java.util.Properties;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.events.EventFiringWebDriver;
+
+import com.applitools.eyes.selenium.Eyes;
+import com.cognifide.qa.bb.junit5.guice.InjectorUtils;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+/**
+ * JUnit 5 extension that provides {@link Eyes} capabilities.
+ */
+public class WithEyesExtension implements BeforeEachCallback, AfterEachCallback {
+
+ /**
+ * Opens an {@link Eyes} session before the test.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ Injector injector = InjectorUtils.retrieveInjectorFromStore(context, NAMESPACE);
+ Properties properties = injector.getInstance(Key.get(Properties.class));
+ WebDriver wrappedDriver = ((EventFiringWebDriver) injector.getInstance(WebDriver.class)).getWrappedDriver();
+ getEyes(context).open(wrappedDriver, String.valueOf(properties.get("eyes.appName")), context.getDisplayName());
+ }
+
+ /**
+ * Closes or aborts the opened {@link Eyes} session.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void afterEach(ExtensionContext context) throws Exception {
+ Eyes eyes = getEyes(context);
+ try {
+ eyes.close();
+ } finally {
+ eyes.abortIfNotClosed();
+ }
+ }
+
+ private Eyes getEyes(ExtensionContext context) {
+ return InjectorUtils.retrieveInjectorFromStore(context, NAMESPACE).getInstance(Eyes.class);
+ }
+}
diff --git a/bb-eyes/src/main/resources/config.yaml b/bb-eyes/src/main/resources/config.yaml
new file mode 100644
index 00000000..afbf8c3b
--- /dev/null
+++ b/bb-eyes/src/main/resources/config.yaml
@@ -0,0 +1,5 @@
+default:
+ properties:
+ eyes.apiKey: ''
+ eyes.fullPageScreenshots: true
+ eyes.appName: ''