diff options
author | Leif Åstrand <leif@vaadin.com> | 2015-09-22 14:24:58 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2015-12-19 13:40:36 +0000 |
commit | 9bc9a8b84be16b7e4b67d84ad25835ff56647ada (patch) | |
tree | d5b2cfda8230da2b01ae94aaa5a49ed6d318a29b | |
parent | bfa4373163ca02b178a6aac258d4cc061cd1abe8 (diff) | |
download | vaadin-framework-9bc9a8b84be16b7e4b67d84ad25835ff56647ada.tar.gz vaadin-framework-9bc9a8b84be16b7e4b67d84ad25835ff56647ada.zip |
Add a UI for reviewing new reference screenshots
Change-Id: I79b953cd4620331e3892a8bd070db8f9bd076e0f
-rw-r--r-- | uitest/src/com/vaadin/screenshotbrowser/ScreenshotBrowser.java | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/uitest/src/com/vaadin/screenshotbrowser/ScreenshotBrowser.java b/uitest/src/com/vaadin/screenshotbrowser/ScreenshotBrowser.java new file mode 100644 index 0000000000..7ed96b38d8 --- /dev/null +++ b/uitest/src/com/vaadin/screenshotbrowser/ScreenshotBrowser.java @@ -0,0 +1,438 @@ +/* + * Copyright 2000-2014 Vaadin 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. + */ +package com.vaadin.screenshotbrowser; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.BeanItemContainer; +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutListener; +import com.vaadin.server.ExternalResource; +import com.vaadin.server.FileResource; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.BrowserFrame; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.CustomComponent; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Table; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +@Theme("valo") +public class ScreenshotBrowser extends UI { + private static final File screenshotDir = findScreenshotDir(); + + /*- + * Groups: + * 1 - test class + * 2 - test method + * 3 - platform + * 4 - browser name + * 5 - browser version + * 6 - additional qualifiers + * 7 - identifier + */ + private static final Pattern screenshotNamePattern = Pattern + .compile("(.+?)-(.+?)_(.+?)_(.+?)_(.+?)(_.+)?_(.+?)\\.png\\.html"); + + public static enum Action { + ACCEPT { + @Override + public void apply(File screenshotFile) { + File targetFile = new File(getReferenceDir(), + screenshotFile.getName()); + + // Delete previous file as well as any alternatives + if (targetFile.exists()) { + for (int i = 1; true; i++) { + File alternative = getAlternative(targetFile, i); + if (alternative.exists()) { + alternative.delete(); + } else { + break; + } + } + targetFile.delete(); + } + + screenshotFile.renameTo(targetFile); + } + }, + IGNORE { + @Override + public void apply(File screenshotFile) { + screenshotFile.delete(); + } + }, + ALTERNATIVE { + @Override + public void apply(File screenshotFile) { + File baseFile = new File(getReferenceDir(), + screenshotFile.getName()); + + // Iterate until we find the first alternative id not yet used + File targetFile = baseFile; + int alternativeNumber = 1; + while (targetFile.exists()) { + targetFile = getAlternative(baseFile, alternativeNumber++); + } + + screenshotFile.renameTo(targetFile); + } + }; + + public void commit(File htmlFile) { + String screenshotName = htmlFile.getName().substring(0, + htmlFile.getName().length() - ".html".length()); + + apply(new File(htmlFile.getParentFile(), screenshotName)); + + htmlFile.delete(); + } + + private static File getReferenceDir() { + return new File(screenshotDir, "reference"); + } + + private static File getAlternative(File baseFile, int alternativeNumber) { + assert alternativeNumber >= 1; + String alternativeName = baseFile.getName().replaceFirst("\\.png", + "_" + alternativeNumber + ".png"); + return new File(baseFile.getParentFile(), alternativeName); + } + + protected abstract void apply(File screenshotFile); + } + + public static class ComparisonFailure { + private final Matcher matcher; + private final File file; + + private Action action; + + public ComparisonFailure(File file) { + this.file = file; + matcher = screenshotNamePattern.matcher(file.getName()); + if (!matcher.matches()) { + throw new RuntimeException("Could not parse screenshot name " + + file.getAbsolutePath()); + } + } + + public File getFile() { + return file; + } + + public String getName() { + return matcher.group(); + } + + public String getTestClass() { + return matcher.group(1); + } + + public String getTestMethod() { + return matcher.group(2); + } + + public String getBrowser() { + return matcher.group(4) + " " + matcher.group(5); + } + + public String getQualifiers() { + return matcher.group(6); + } + + public String getIdentifier() { + return matcher.group(7); + } + + public void setAction(Action action) { + this.action = action; + } + + public Action getAction() { + return action; + } + } + + private class Viewer extends CustomComponent { + private BrowserFrame preview = new BrowserFrame(); + private VerticalLayout left = new VerticalLayout(); + private HorizontalLayout root = new HorizontalLayout(left, preview); + + private Collection<ComparisonFailure> items; + + public Viewer() { + preview.setWidth("1500px"); + preview.setHeight("100%"); + + left.setMargin(true); + left.setSpacing(true); + left.setSizeFull(); + + left.addComponent(createActionButton("Accept changes", 'j', + Action.ACCEPT)); + left.addComponent(createActionButton("Ignore changes", 'k', + Action.IGNORE)); + left.addComponent(createActionButton("Use as alternative", 'l', + Action.ALTERNATIVE)); + left.addComponent(new Button("Clear action", + createSetActionListener(null))); + + left.addComponent(createSpacer()); + left.addComponent(new Button("Commit actions", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + commitActions(); + } + })); + + left.addComponent(createSpacer()); + left.addComponent(new Button("Refresh", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + refreshTableContainer(); + } + })); + + Label expandSpacer = createSpacer(); + left.addComponent(expandSpacer); + left.setExpandRatio(expandSpacer, 1); + + left.addComponent(new Label( + "Press the j, k or l keys to quickly select an action for the selected item.")); + + root.setExpandRatio(left, 1); + root.setSizeFull(); + + setCompositionRoot(root); + setHeight("850px"); + setWidth("100%"); + } + + private Button createActionButton(String caption, char shortcut, + Action action) { + Button button = new Button(caption + " <strong>" + shortcut + + "</strong>", createSetActionListener(action)); + button.setCaptionAsHtml(true); + return button; + } + + private Label createSpacer() { + // Poor man's spacer, non-breaking space + return new Label("\u00a0"); + } + + private ClickListener createSetActionListener(final Action action) { + return new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + setActions(action); + } + }; + } + + public void setActions(final Action action) { + for (ComparisonFailure comparisonFailure : items) { + comparisonFailure.setAction(action); + } + table.refreshRowCache(); + } + + public void setItems(Collection<ComparisonFailure> items) { + this.items = items; + + if (items.size() == 1) { + ComparisonFailure failure = items.iterator().next(); + preview.setSource(new FileResource(failure.getFile())); + } else { + preview.setSource(new ExternalResource("about:blank")); + } + } + } + + private final Table table = new Table(); + private final Viewer viewer = new Viewer(); + + @Override + protected void init(VaadinRequest request) { + table.setWidth("100%"); + table.setHeight("100%"); + + table.setMultiSelect(true); + table.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(ValueChangeEvent event) { + @SuppressWarnings("unchecked") + Collection<ComparisonFailure> selectedRows = (Collection<ComparisonFailure>) table + .getValue(); + + viewer.setItems(selectedRows); + } + }); + + table.addShortcutListener(createShortcutListener(KeyCode.J, + Action.ACCEPT)); + table.addShortcutListener(createShortcutListener(KeyCode.K, + Action.IGNORE)); + table.addShortcutListener(createShortcutListener(KeyCode.L, + Action.ALTERNATIVE)); + + refreshTableContainer(); + + VerticalLayout mainLayout = new VerticalLayout(table, viewer); + mainLayout.setExpandRatio(table, 1); + mainLayout.setSizeFull(); + + setSizeFull(); + setContent(mainLayout); + + table.focus(); + } + + private void commitActions() { + for (ComparisonFailure comparisonFailure : getContainer().getItemIds()) { + Action action = comparisonFailure.getAction(); + if (action != null) { + action.commit(comparisonFailure.getFile()); + } + } + + refreshTableContainer(); + } + + private ShortcutListener createShortcutListener(int keyCode, + final Action action) { + return new ShortcutListener(action.toString(), keyCode, null) { + @Override + public void handleAction(Object sender, Object target) { + viewer.setActions(action); + selectNextWithoutAction(); + } + }; + } + + private void selectNextWithoutAction() { + Collection<?> selected = (Collection<?>) table.getValue(); + BeanItemContainer<ComparisonFailure> container = getContainer(); + + // Find where to start + ComparisonFailure candidate; + if (selected == null || selected.isEmpty()) { + candidate = container.firstItemId(); + } else { + candidate = (ComparisonFailure) selected.iterator().next(); + } + + // Find first one without action + while (candidate != null && candidate.getAction() != null) { + candidate = container.nextItemId(candidate); + } + + // Select it + if (candidate == null) { + table.setValue(Collections.emptySet()); + } else { + table.setValue(Collections.singleton(candidate)); + } + } + + @SuppressWarnings("unchecked") + private BeanItemContainer<ComparisonFailure> getContainer() { + return (BeanItemContainer<ComparisonFailure>) table + .getContainerDataSource(); + } + + private void refreshTableContainer() { + File errorsDir = new File(screenshotDir, "errors"); + + File[] failures = errorsDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".html"); + } + }); + + BeanItemContainer<ComparisonFailure> container = new BeanItemContainer<ComparisonFailure>( + ComparisonFailure.class); + for (File failure : failures) { + container.addBean(new ComparisonFailure(failure)); + } + + table.setContainerDataSource(container); + table.setVisibleColumns("testClass", "testMethod", "browser", + "qualifiers", "identifier", "action"); + if (container.size() > 0) { + table.select(container.firstItemId()); + } + } + + private static File findScreenshotDir() { + File propertiesFile = new File( + "work/eclipse-run-selected-test.properties"); + if (!propertiesFile.exists()) { + throw new RuntimeException("File " + + propertiesFile.getAbsolutePath() + " not found."); + } + + FileInputStream in = null; + try { + in = new FileInputStream(propertiesFile); + + Properties properties = new Properties(); + properties.load(in); + String screenShotDirName = properties + .getProperty("com.vaadin.testbench.screenshot.directory"); + if (screenShotDirName == null || screenShotDirName.startsWith("<")) { + throw new RuntimeException( + "com.vaadin.testbench.screenshot.directory has not been configred in " + + propertiesFile.getAbsolutePath()); + } + File screenshotDir = new File(screenShotDirName); + if (!screenshotDir.isDirectory()) { + throw new RuntimeException(screenshotDir.getAbsolutePath() + + " is not a directory"); + } + return screenshotDir; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} |