/*
* Copyright 2016 GoDataDriven B.V.
*
* 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.
*/
package io.divolte.server;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import io.divolte.server.ServerTestUtils.EventPayload;
import io.divolte.server.config.BrowserSourceConfiguration;
import org.junit.Test;
import org.openqa.selenium.By;
import javax.annotation.ParametersAreNonnullByDefault;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.divolte.server.IncomingRequestProcessor.DUPLICATE_EVENT_KEY;
import static io.divolte.server.SeleniumTestBase.TEST_PAGES.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ParametersAreNonnullByDefault
public class SeleniumJavaScriptTest extends SeleniumTestBase {
@Test
public void shouldRegenerateIDsOnExplicitNavigation() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
// do a sequence of explicit navigation by setting the browser location
// and then check that all requests generated a unique pageview ID
final Runnable[] actions = {
() -> driver.navigate().to(urlOf(BASIC)),
() -> driver.navigate().to(urlOf(BASIC_COPY)),
() -> driver.navigate().to(urlOf(BASIC))
};
final int numberOfUniquePageViewIDs = uniquePageViewIdsForSeriesOfActions(actions);
assertEquals(actions.length, numberOfUniquePageViewIDs);
}
@Test
public void shouldRegenerateIDsOnRefresh() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
// Navigate to the same page twice
final Runnable[] actions = {
() -> driver.get(urlOf(BASIC)),
driver.navigate()::refresh
};
final int numberOfUniquePageViewIDs = uniquePageViewIdsForSeriesOfActions(actions);
assertEquals(actions.length, numberOfUniquePageViewIDs);
}
@Test
public void shouldRegenerateIDsOnForwardBackNavigation() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
// Navigate to the same page twice
final Runnable[] actions = {
() -> driver.get(urlOf(BASIC)),
() -> driver.get(urlOf(BASIC_COPY)),
() -> driver.get(urlOf(BASIC)),
driver.navigate()::back,
driver.navigate()::back,
driver.navigate()::forward,
driver.navigate()::back,
driver.navigate()::forward,
driver.navigate()::forward
};
final int numberOfUniquePageViewIDs = uniquePageViewIdsForSeriesOfActions(actions);
assertEquals(actions.length, numberOfUniquePageViewIDs);
}
@Test
public void shouldGenerateIDsOnComplexSeriesOfEvents() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
// Navigate to the same page twice
final Runnable[] actions = {
() -> driver.get(urlOf(BASIC)),
() -> driver.get(urlOf(BASIC_COPY)),
() -> driver.get(urlOf(BASIC)),
() -> driver.get(urlOf(BASIC_COPY)),
() -> driver.get(urlOf(BASIC)),
driver.navigate()::back,
driver.navigate()::back,
() -> driver.findElement(By.id("custom")).click(),
driver.navigate()::forward,
driver.navigate()::refresh,
driver.navigate()::back,
() -> driver.get(urlOf(PAGE_VIEW_SUPPLIED)),
driver.navigate()::back
};
// We expect one duplicate PV ID, because of the custom event
final int numberOfUniquePageViewIDs = uniquePageViewIdsForSeriesOfActions(actions);
assertEquals(actions.length - 1, numberOfUniquePageViewIDs);
}
private int uniquePageViewIdsForSeriesOfActions(final Runnable[] actions) {
return Stream.of(actions)
.flatMap((action) -> {
action.run();
final EventPayload payload = unchecked(server::waitForEvent);
final DivolteEvent event = payload.event;
return event.browserEventData.map(b -> b.pageViewId).map(Stream::of).orElse(null);
})
.collect(Collectors.toSet()).size();
}
@FunctionalInterface
private interface ExceptionSupplier<T> {
T supply() throws Exception;
}
private static <T> T unchecked(final ExceptionSupplier<T> supplier) {
try {
return supplier.supply();
} catch (final RuntimeException e) {
// Pass through as-is;
throw e;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void shouldSignalWhenOpeningPage() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
final String location = urlOf(BASIC);
driver.get(location);
final EventPayload payload = server.waitForEvent();
final DivolteEvent eventData = payload.event;
final Boolean detectedDuplicate = payload.event.exchange.getAttachment(DUPLICATE_EVENT_KEY);
assertFalse(eventData.corruptEvent);
assertFalse(detectedDuplicate);
assertFalse(Strings.isNullOrEmpty(eventData.partyId.value));
assertTrue(eventData.newPartyId);
assertFalse(Strings.isNullOrEmpty(eventData.sessionId.value));
assertTrue(eventData.firstInSession);
assertTrue(eventData.browserEventData.isPresent());
final DivolteEvent.BrowserEventData browserEventData = eventData.browserEventData.get();
assertFalse(Strings.isNullOrEmpty(browserEventData.pageViewId));
assertFalse(Strings.isNullOrEmpty(eventData.eventId));
assertTrue(eventData.eventType.isPresent());
assertEquals("pageView", eventData.eventType.get());
assertTrue(browserEventData.location.isPresent());
assertEquals(location, browserEventData.location.get());
/*
* We don't really know anything about the clock on the executing browser,
* but we'd expect it to be a reasonably accurate clock on the same planet.
* So, if it is within +/- 12 hours of our clock, we think it's fine.
*/
final Instant now = Instant.now();
assertThat(eventData.clientTime,
allOf(greaterThan(now.minus(12, ChronoUnit.HOURS)), lessThan(now.plus(12, ChronoUnit.HOURS))));
/*
* Doing true assertions against the viewport and window size
* is problematic on different devices, as the number do not
* always make sense on SauceLabs. Also, sometimes the window
* is partially outside of the screen view port or other strange
* things. It gets additionally complicated on mobile devices.
*
* Hence, we just check whether these are integers greater than 50.
*/
assertTrue(browserEventData.viewportPixelWidth.isPresent());
assertThat(browserEventData.viewportPixelWidth.get(), greaterThan(50));
assertTrue(browserEventData.viewportPixelHeight.isPresent());
assertThat(browserEventData.viewportPixelHeight.get(), greaterThan(50));
assertTrue(browserEventData.screenPixelWidth.isPresent());
assertThat(browserEventData.screenPixelWidth.get(), greaterThan(50));
assertTrue(browserEventData.screenPixelHeight.isPresent());
assertThat(browserEventData.screenPixelHeight.get(), greaterThan(50));
}
@Test
public void shouldSendCustomEvent() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
driver.get(urlOf(BASIC));
server.waitForEvent();
driver.findElement(By.id("custom")).click();
final EventPayload payload = server.waitForEvent();
final DivolteEvent eventData = payload.event;
assertTrue(eventData.eventType.isPresent());
assertEquals("custom", eventData.eventType.get());
final Optional<String> customEventParameters =
eventData.eventParametersProducer.get().map(Object::toString);
assertTrue(customEventParameters.isPresent());
assertEquals("{\"a\":{},\"b\":\"c\",\"d\":{\"a\":[],\"b\":\"g\"},\"e\":[\"1\",\"2\"],\"f\":42,\"g\":53.2,\"h\":-37,\"i\":-7.83E-9,\"j\":true,\"k\":false,\"l\":null,\"m\":\"2015-06-13T15:49:33.002Z\",\"n\":{},\"o\":[{},{\"a\":\"b\"},{\"c\":\"d\"}],\"p\":{}}",
customEventParameters.get());
}
@Test
public void shouldSetAppropriateCookies() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
driver.get(urlOf(BASIC));
server.waitForEvent();
final Optional<DivolteIdentifier> parsedPartyCookieOption = DivolteIdentifier.tryParse(driver.manage().getCookieNamed(BrowserSourceConfiguration.DEFAULT_BROWSER_SOURCE_CONFIGURATION.partyCookie).getValue());
assertTrue(parsedPartyCookieOption.isPresent());
assertThat(
parsedPartyCookieOption.get(),
isA(DivolteIdentifier.class));
final Optional<DivolteIdentifier> parsedSessionCookieOption = DivolteIdentifier.tryParse(driver.manage().getCookieNamed(BrowserSourceConfiguration.DEFAULT_BROWSER_SOURCE_CONFIGURATION.sessionCookie).getValue());
assertTrue(parsedSessionCookieOption.isPresent());
assertThat(
parsedSessionCookieOption.get(),
isA(DivolteIdentifier.class));
}
@Test
public void shouldPickupProvidedPageViewIdFromHash() throws Exception {
doSetUp();
Preconditions.checkState(null != driver && null != server);
driver.get(urlOf(PAGE_VIEW_SUPPLIED));
final EventPayload payload = server.waitForEvent();
final DivolteEvent eventData = payload.event;
assertEquals("supercalifragilisticexpialidocious", eventData.browserEventData.get().pageViewId);
assertEquals("supercalifragilisticexpialidocious0", eventData.eventId);
}
@Test
public void shouldSupportCustomJavascriptName() throws Exception {
doSetUp("selenium-test-custom-javascript-name.conf");
Preconditions.checkState(null != driver && null != server);
driver.get(urlOf(CUSTOM_JAVASCRIPT_NAME));
final EventPayload payload = server.waitForEvent();
final DivolteEvent eventData = payload.event;
assertEquals("pageView", eventData.eventType.get());
}
@Test
public void shouldUseConfiguredEventSuffix() throws Exception {
doSetUp("selenium-test-custom-event-suffix.conf");
Preconditions.checkState(null != driver && null != server);
driver.get(urlOf(BASIC));
final EventPayload payload = server.waitForEvent();
final DivolteEvent eventData = payload.event;
assertEquals("pageView", eventData.eventType.get());
}
}