Skip to content

Commit

Permalink
Merge pull request #9 from jLubi/master
Browse files Browse the repository at this point in the history
Support Selenium Grid, Screenshot-Name
  • Loading branch information
Frisch12 authored Jun 25, 2019
2 parents c38ece9 + b3deec1 commit 2c630ea
Show file tree
Hide file tree
Showing 14 changed files with 282 additions and 123 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ selenium.browser.height=1080
selenium.browser.width=1920
```

Support for Remote Selenium Grid (instead of local browser) - sample grid configuration: docker-compose.grid.yml.
```properties
selenium.useGrid=true
selenium.gridURL=http://localhost:4444/wd/hub
selenium.browser=chrome
selenium.platform=linux
```

Additional configuration such as output directory and timestamp format wich will be rendered inside of the screenshots (if desired)
```properties
screenshot.outputDirectory=screenshots
Expand Down
29 changes: 29 additions & 0 deletions docker-compose.grid.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: "3"
services:
selenium-hub:
image: selenium/hub:3.141.59-radium
container_name: selenium-hub
ports:
- "4444:4444"
chrome:
image: selenium/node-chrome:3.141.59-radium
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- HUB_HOST=selenium-hub
- HUB_PORT=4444
- NODE_MAX_INSTANCES=10
- NODE_MAX_SESSION=10
firefox:
image: selenium/node-firefox:3.141.59-radium
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- HUB_HOST=selenium-hub
- HUB_PORT=4444
- NODE_MAX_INSTANCES=10
- NODE_MAX_SESSION=10
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@

import io.freefair.screenshot_scheduler.models.Screenshot;
import io.freefair.screenshot_scheduler.repositories.ScreenshotRepository;
import io.freefair.screenshot_scheduler.selenium.SeleniumScheduler;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import javax.transaction.Transactional;
import java.io.File;
Expand All @@ -24,50 +17,50 @@
@RequestMapping("/screenshot")
public class ScreenshotController {

private final ScreenshotRepository screenshotRepository;
private final ScreenshotRepository screenshotRepository;

@Value("${screenshot.outputDirectory}")
private String outputDirectory;
@Value("${screenshot.outputDirectory}")
private String outputDirectory;

@Autowired
public ScreenshotController(ScreenshotRepository screenshotRepository) {
this.screenshotRepository = screenshotRepository;
}
@Autowired
public ScreenshotController(ScreenshotRepository screenshotRepository) {
this.screenshotRepository = screenshotRepository;
}

@RequestMapping(method = RequestMethod.GET)
public List<Screenshot> getAll() {
return screenshotRepository.findAll();
}
@RequestMapping(method = RequestMethod.GET)
public List<Screenshot> getAll() {
return screenshotRepository.findAll();
}

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Screenshot getById(@PathVariable("id") UUID id) {
return screenshotRepository.findById(id).orElse(null);
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Screenshot getById(@PathVariable("id") UUID id) {
return screenshotRepository.findById(id).orElse(null);
}

@RequestMapping(method = RequestMethod.POST)
@Transactional
public Screenshot create(@RequestBody Screenshot screenshot) {
return screenshotRepository.saveAndFlush(screenshot);
}
@RequestMapping(method = RequestMethod.POST)
@Transactional
public Screenshot create(@RequestBody Screenshot screenshot) {
return screenshotRepository.saveAndFlush(screenshot);
}

@RequestMapping(method = RequestMethod.PUT)
@Transactional
public Screenshot update(@RequestBody Screenshot screenshot) {
return screenshotRepository.saveAndFlush(screenshot);
}
@RequestMapping(method = RequestMethod.PUT)
@Transactional
public Screenshot update(@RequestBody Screenshot screenshot) {
return screenshotRepository.saveAndFlush(screenshot);
}

@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@Transactional
public void delete(@PathVariable("id") UUID id) {
screenshotRepository.deleteById(id);
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@Transactional
public void delete(@PathVariable("id") UUID id) {
screenshotRepository.deleteById(id);
}

@RequestMapping(value = "/{id}/image", method = RequestMethod.GET, produces = "image/png")
@ResponseBody
public byte[] getImage(@PathVariable("id") UUID id) throws IOException {
File file = new File(new File(outputDirectory), id.toString() + ".png");
if(!file.exists())
throw new NotFoundException("File " + file.getName() + " not found!");
return FileUtils.readFileToByteArray(file);
}
@RequestMapping(value = "/{id}/image", method = RequestMethod.GET, produces = "image/png")
@ResponseBody
public byte[] getImage(@PathVariable("id") UUID id) throws IOException {
File file = new File(new File(outputDirectory), id.toString() + ".png");
if (!file.exists())
throw new NotFoundException("File " + file.getName() + " not found!");
return FileUtils.readFileToByteArray(file);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class Screenshot {
@Column(columnDefinition = "varchar(36)")
private UUID id;

private String name;

private String url;

private String loginUrl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void join() throws InterruptedException {
public void run() {
try {
((RemoteWebDriver)scheduledSeleniumSession.getSession().getDriver()).executeScript("window.scrollTo(0, " + scheduledSeleniumSession.getYScroll() + ");");
helper.createScreenshot(scheduledSeleniumSession.getSession().getDriver(), new File(new File(outputDirectory), screenshot.getId().toString() + ".png"), screenshot.isTimestamp());
helper.createScreenshot(scheduledSeleniumSession.getSession().getDriver(), outputDirectory, screenshot);
} catch (Exception e) {
scheduledSeleniumSession.delete();
log.error("Error while creating screenshot. Stopping Session!", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.freefair.screenshot_scheduler.selenium;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.MalformedURLException;
import java.net.URL;

@Getter
@Slf4j
@Component
public class SeleniumConfiguration {
@Value("${selenium.useGrid}")
private boolean useGrid;

@Value("${selenium.gridURL}")
private String gridURL;

@Value("${selenium.browser}")
private String browser;

@Value("${selenium.platform}")
private String platform;

@Value("${selenium.implicitWait}")
private int implicitlyWait;

@Value("${selenium.browser.height}")
private int height;

@Value("${selenium.browser.width}")
private int width;

@Value("#{'${selenium.chrome_args}'.trim().split(' ')}")
private String[] chromeArgs;

public URL getGridURL() {
try {
return new URL(gridURL);
} catch (MalformedURLException e) {
log.error("GridURL is invalid", e);
return null;
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.freefair.screenshot_scheduler.selenium;

import io.freefair.screenshot_scheduler.models.Screenshot;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
Expand All @@ -24,43 +25,81 @@

@Component
public class SeleniumHelper {
@Value("${screenshot.date-format}")
private String dateFormat;

@Value("${screenshot.date-font-size}")
private float fontSize;

@Value("${screenshot.time_zone}")
private String timeZone;

public void createScreenshot(WebDriver driver, File outputFile, boolean withTimestamp) throws IOException {
byte[] screenshotAs = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
if (withTimestamp) {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(screenshotAs));
Graphics graphics = image.getGraphics();
Font font;
try {
font = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(SeleniumHelper.class.getClassLoader().getResourceAsStream("Helvetica.ttf")))
.deriveFont(fontSize);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
String time = ZonedDateTime.now(TimeZone.getTimeZone(timeZone).toZoneId()).format(DateTimeFormatter.ofPattern(dateFormat));
Rectangle2D stringBounds = graphics.getFontMetrics(font).getStringBounds(time, graphics);
int stringWidth = (int) stringBounds.getWidth();
int imageWidth = image.getWidth();
int stringHeight = (int) stringBounds.getHeight();
int imageHeight = image.getHeight();
graphics.setColor(Color.WHITE);
graphics.fillRect((imageWidth - stringWidth - 10), (imageHeight - stringHeight - 5), (stringWidth + 10), (stringHeight + 5));

graphics.setColor(Color.BLACK);
graphics.setFont(font);
graphics.drawString(time, (imageWidth - stringWidth - 5), (imageHeight - 3));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", byteArrayOutputStream);
screenshotAs = byteArrayOutputStream.toByteArray();
}
FileUtils.writeByteArrayToFile(outputFile, screenshotAs);
}
@Value("${screenshot.date-format}")
private String dateFormat;

@Value("${screenshot.date-font-size}")
private float fontSize;

@Value("${screenshot.time_zone}")
private String timeZone;

private void fillImageWithText(BufferedImage image, String text, CaptionPosition position) {
Font font;
try {
font = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(SeleniumHelper.class.getClassLoader().getResourceAsStream("Helvetica.ttf")))
.deriveFont(fontSize);
} catch (Exception ex) {
throw new RuntimeException(ex);
}

Graphics graphics = image.getGraphics();

Rectangle2D stringBounds = graphics.getFontMetrics(font).getStringBounds(text, graphics);
int stringWidth = (int) stringBounds.getWidth();
int imageWidth = image.getWidth();
int stringHeight = (int) stringBounds.getHeight();
int imageHeight = image.getHeight();
graphics.setColor(Color.WHITE);

switch (position) {
case LOWER_RIGHT:
graphics.fillRect((imageWidth - stringWidth - 10), (imageHeight - stringHeight - 5), (stringWidth + 10), (stringHeight + 5));
break;
case LOWER_LEFT:
graphics.fillRect(0, (imageHeight - stringHeight - 5), (stringWidth + 10), (stringHeight + 5));
break;
}
graphics.setColor(Color.BLACK);
graphics.setFont(font);

switch (position) {
case LOWER_RIGHT:
graphics.drawString(text, (imageWidth - stringWidth - 5), (imageHeight - 3));
break;
case LOWER_LEFT:
graphics.drawString(text, 5, (imageHeight - 3));
break;
}
}

public void createScreenshot(WebDriver driver, String outputDirectory, Screenshot screenshotConfiguration) throws
IOException {
byte[] screenshotAs = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
if (screenshotConfiguration.isTimestamp()) {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(screenshotAs));

String time = ZonedDateTime.now(TimeZone.getTimeZone(timeZone).toZoneId()).format(DateTimeFormatter.ofPattern(dateFormat));

fillImageWithText(image, time, CaptionPosition.LOWER_RIGHT);

String humanName = screenshotConfiguration.getName();
if (humanName != null && !humanName.isEmpty()) {
fillImageWithText(image, humanName, CaptionPosition.LOWER_LEFT);
}

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", byteArrayOutputStream);
screenshotAs = byteArrayOutputStream.toByteArray();
}
FileUtils.writeByteArrayToFile(new File(new File(outputDirectory), screenshotConfiguration.getId().toString() + ".png"), screenshotAs);
}

enum CaptionPosition {
LOWER_LEFT,
LOWER_RIGHT
}

}


Loading

0 comments on commit 2c630ea

Please sign in to comment.