Project

General

Profile

Download (16.2 KB) Statistics
| Branch: | Tag: | Revision:
1
package eu.etaxonomy.dataportal.pages;
2

    
3
import static org.junit.Assert.assertTrue;
4

    
5
import java.io.File;
6
import java.io.IOException;
7
import java.lang.reflect.Constructor;
8
import java.net.MalformedURLException;
9
import java.net.URL;
10
import java.util.ArrayList;
11
import java.util.List;
12
import java.util.concurrent.TimeUnit;
13
import java.util.regex.Matcher;
14
import java.util.regex.Pattern;
15
import java.util.stream.Collectors;
16

    
17
import org.apache.commons.io.FileUtils;
18
import org.apache.log4j.Logger;
19
import org.openqa.selenium.By;
20
import org.openqa.selenium.NoSuchElementException;
21
import org.openqa.selenium.OutputType;
22
import org.openqa.selenium.TakesScreenshot;
23
import org.openqa.selenium.WebDriver;
24
import org.openqa.selenium.WebElement;
25
import org.openqa.selenium.interactions.Actions;
26
import org.openqa.selenium.support.CacheLookup;
27
import org.openqa.selenium.support.FindBy;
28
import org.openqa.selenium.support.FindBys;
29
import org.openqa.selenium.support.PageFactory;
30
import org.openqa.selenium.support.ui.WebDriverWait;
31

    
32
import com.google.common.base.Function;
33

    
34
import eu.etaxonomy.dataportal.DataPortalContext;
35
import eu.etaxonomy.dataportal.elements.BaseElement;
36
import eu.etaxonomy.dataportal.elements.ClassificationTreeBlock;
37
import eu.etaxonomy.dataportal.elements.LinkElement;
38
import eu.etaxonomy.dataportal.selenium.JUnitWebDriverWait;
39
import eu.etaxonomy.dataportal.selenium.UrlLoaded;
40

    
41
/**
42
 * FIXME only works with the cichorieae theme
43
 *
44
 * @author a.kohlbecker
45
 *
46
 */
47
public abstract class PortalPage {
48

    
49
    /**
50
     *
51
     */
52
    public static final int WAIT_SECONDS = 25;
53

    
54
    public static final Logger logger = Logger.getLogger(PortalPage.class);
55

    
56
    protected final static String DRUPAL_PAGE_QUERY = "q=";
57

    
58
    private final static Pattern DRUPAL_PAGE_QUERY_PATTERN = Pattern.compile("q=([^&]*)");
59

    
60
    public enum MessageType {status, warning, error} // needs to be lowercase
61

    
62
    protected WebDriver driver;
63

    
64
    protected DataPortalContext context;
65

    
66
    protected final JUnitWebDriverWait wait;
67

    
68
    public WebDriverWait getWait() {
69
        return wait;
70
    }
71

    
72

    
73
    /**
74
     * Implementations of this method will supply the relative
75
     * path to the Drupal page. This path will usually have the form
76
     * <code>cdm_dataportal/{nodetype}</code>. For example the taxon pages all
77
     * have the page base <code>cdm_dataportal/taxon</code>
78
     */
79
    protected abstract String getDrupalPageBase();
80

    
81
    private String initialDrupalPagePath;
82

    
83
    protected URL pageUrl;
84

    
85
    // ==== WebElements === //
86

    
87
    @FindBy(className="node")
88
    protected WebElement portalContent;
89

    
90
//    @FindBy(tagName="title")
91
//    @CacheLookup
92
//    protected WebElement title;
93

    
94
    @FindBy(className="node")
95
    protected WebElement node;
96

    
97
    @FindBys({@FindBy(id="tabs-wrapper"), @FindBy(className="primary")})
98
    @CacheLookup
99
    protected WebElement primaryTabs;
100

    
101
    @FindBy(id="block-cdm-dataportal-2")
102
    @CacheLookup
103
    protected WebElement searchBlockElement;
104

    
105
    @FindBy(id="block-cdm-taxontree-cdm-tree")
106
    @CacheLookup
107
    protected WebElement classificationBrowserBlock;
108

    
109
    @FindBy(className="messages")
110
    @CacheLookup
111
    protected List<WebElement> messages;
112

    
113
    /**
114
     * Creates a new PortaPage. Implementations of this class will provide the base path of the page by
115
     * implementing the method {@link #getDrupalPageBase()}. The constructor argument <code>pagePathSuffix</code>
116
     * specifies the specific page to navigate to. For example:
117
     * <ol>
118
     * <li>{@link #getDrupalPageBase()} returns <code>/cdm_dataportal/taxon</code></li>
119
     * <li><code>pagePathSuffix</code> gives <code>7fe8a8b6-b0ba-4869-90b3-177b76c1753f</code></li>
120
     * </ol>
121
     * Both are combined to form the URL pathelement <code>/cdm_dataportal/taxon/7fe8a8b6-b0ba-4869-90b3-177b76c1753f</code>
122
     *
123
     */
124
    public PortalPage(WebDriver driver, DataPortalContext context, String pagePathSuffix) throws MalformedURLException {
125

    
126
        this.driver = driver;
127

    
128
        this.context = context;
129

    
130
        this.wait = new JUnitWebDriverWait(driver, WAIT_SECONDS);
131

    
132
        this.initialDrupalPagePath = getDrupalPageBase() + (pagePathSuffix != null ? "/" + pagePathSuffix: "");
133

    
134
        this.pageUrl = new URL(context.getBaseUri().toString() + "?" + DRUPAL_PAGE_QUERY + initialDrupalPagePath);
135

    
136
        // tell browser to navigate to the page
137
        driver.get(pageUrl.toString());
138

    
139
        takeScreenShot();
140

    
141
        // This call sets the WebElement fields.
142
        PageFactory.initElements(driver, this);
143

    
144
        logger.info("loading " + pageUrl);
145

    
146
        try {
147
            String ignore_error = "Expecting web service to return pager objects but received an array";
148
            List<String> errors = getErrors().stream().filter(str -> str.startsWith(ignore_error)).collect(Collectors.toList());
149
            assertTrue("The page must not show an error box", errors.size() == 0);
150
        } catch (NoSuchElementException e) {
151
            //IGNORE since this is expected!
152
        }
153

    
154
    }
155

    
156
    /**
157
     * Creates a new PortaPage at given URL location. An Exception is thrown if
158
     * this URL is not matching the expected URL for the specific page type.
159
     *
160
     */
161
    public PortalPage(WebDriver driver, DataPortalContext context, URL url) throws Exception {
162

    
163
        this.driver = driver;
164

    
165
        this.context = context;
166

    
167
        this.wait = new JUnitWebDriverWait(driver, 25);
168

    
169
        this.pageUrl = new URL(context.getBaseUri().toString());
170

    
171
        // tell browser to navigate to the given URL
172
        driver.get(url.toString());
173

    
174
        takeScreenShot();
175

    
176
        if(!isOnPage()){
177
            throw new Exception("Not on the expected portal page ( current: " + driver.getCurrentUrl() + ", expected: " +  pageUrl + " )");
178
        }
179

    
180
        this.pageUrl = url;
181

    
182
        logger.info("loading " + pageUrl);
183

    
184
        // This call sets the WebElement fields.
185
        PageFactory.initElements(driver, this);
186

    
187
    }
188

    
189
    /**
190
     * Creates a new PortaPage at the WebDrivers current URL location. An Exception is thrown if
191
     * driver.getCurrentUrl() is not matching the expected URL for the specific page type.
192
     *
193
     */
194
    public PortalPage(WebDriver driver, DataPortalContext context) throws Exception {
195

    
196
        this.driver = driver;
197

    
198
        this.context = context;
199

    
200
        this.wait = new JUnitWebDriverWait(driver, 25);
201

    
202
        // preliminary set the pageUrl to the base path of this page, this is used in the next setp to check if the
203
        // driver.getCurrentUrl() is a sub path of the base path
204
        this.pageUrl = new URL(context.getBaseUri().toString());
205

    
206
        takeScreenShot();
207

    
208
        if(!isOnPage()){
209
            throw new Exception("Not on the expected portal page ( current: " + driver.getCurrentUrl() + ", expected: " +  pageUrl + " )");
210
        }
211

    
212
        // now set the real URL
213
        this.pageUrl = new URL(driver.getCurrentUrl());
214

    
215
        logger.info("loading " + pageUrl);
216

    
217
        // This call sets the WebElement fields.
218
        PageFactory.initElements(driver, this);
219

    
220
    }
221

    
222

    
223
    protected boolean isOnPage() {
224
        return driver.getCurrentUrl().startsWith(pageUrl.toString());
225
    }
226

    
227
    /**
228
     * navigate and reload the page if not jet there
229
     */
230
    public void get() {
231
        if(!driver.getCurrentUrl().equals(pageUrl.toString())){
232
            driver.get(pageUrl.toString());
233
            wait.until(new UrlLoaded(pageUrl.toString()));
234
            // take screenshot of new page
235
            takeScreenShot();
236
            PageFactory.initElements(driver, this);
237
        }
238
    }
239

    
240
    /**
241
     * go back in history
242
     */
243
    public void back() {
244
        driver.navigate().back();
245
    }
246

    
247
    public String getDrupalPagePath() {
248
        URL currentURL;
249
        try {
250
            currentURL = new URL(driver.getCurrentUrl());
251
            if(currentURL.getQuery() != null && currentURL.getQuery().contains(DRUPAL_PAGE_QUERY)){
252
                Matcher m = DRUPAL_PAGE_QUERY_PATTERN.matcher(currentURL.getQuery());
253
                m.matches();
254
                return m.group(1);
255
            } else {
256
                String uriBasePath = context.getBaseUri().getPath();
257
                if(!uriBasePath.endsWith("/")){
258
                    uriBasePath += "/";
259
                }
260
                return currentURL.getPath().replaceFirst("^"+ uriBasePath, "");
261
            }
262
        } catch (MalformedURLException e) {
263
            throw new RuntimeException(e);
264
        }
265
    }
266

    
267
    /**
268
     * Returns the the page path with which the page has initially been loaded.
269
     * Due to redirects the actual page path which can be retrieved by
270
     * {@link #getDrupalPagePath()} might be different
271
     *
272
     */
273
    public String getInitialDrupalPagePath() {
274
        return initialDrupalPagePath;
275
    }
276

    
277
    /**
278
     *
279
     * @return the page title
280
     * @deprecated use {@link WebDriver#getTitle()}
281
     */
282
    @Deprecated
283
    public String getTitle() {
284
        return driver.getTitle();
285
    }
286

    
287
    public List<String> getMessageItems(MessageType messageType){
288
        if(messages != null){
289
            for(WebElement m : messages){
290
                if(m.getAttribute("class").contains(messageType.name())){
291
                    List<WebElement> messageItems = m.findElements(By.cssSelector("ul.messages__list li"));
292
                    return messageItems.stream().map(mi -> mi.getText()).collect(Collectors.toList());
293
                }
294
            }
295
        }
296
        return new ArrayList<>();
297
    }
298

    
299
    /**
300
     * @return the warning messages from the Drupal message box
301
     */
302
    public List<String> getWarnings() {
303
        return getMessageItems(MessageType.warning);
304
    }
305

    
306
    /**
307
     * @return the error messages from the Drupal message box
308
     */
309
    public List<String> getErrors() {
310
        return getMessageItems(MessageType.error);
311
    }
312

    
313
    public String getAuthorInformationText() {
314

    
315
        WebElement authorInformation = null;
316

    
317
        try {
318
            authorInformation  = node.findElement(By.className("submitted"));
319
        } catch (NoSuchElementException e) {
320
            // IGNORE //
321
        }
322

    
323

    
324
        if(authorInformation != null){
325
            return authorInformation.getText();
326
        } else {
327
            return null;
328
        }
329
    }
330

    
331
    public List<LinkElement> getPrimaryTabs(){
332
        List<LinkElement> tabs = new ArrayList<LinkElement>();
333
        List<WebElement> links = primaryTabs.findElements(By.tagName("a"));
334
        for(WebElement a : links) {
335
            WebElement renderedLink = a;
336
            if(renderedLink.isDisplayed()){
337
                tabs.add(new LinkElement(renderedLink));
338
            }
339
        }
340

    
341
        return tabs;
342
    }
343

    
344
    public ClassificationTreeBlock getClassificationTree() {
345
        return new ClassificationTreeBlock(classificationBrowserBlock);
346
    }
347

    
348
    public void hover(WebElement element) {
349
        Actions actions = new Actions(driver);
350
        actions.moveToElement(element, 1, 1).perform();
351
        logger.debug("hovering");
352
    }
353

    
354

    
355
    /**
356
     * @return the current URL string from the {@link WebDriver}
357
     */
358
    public URL getPageURL() {
359
        return pageUrl;
360
    }
361

    
362

    
363
    /**
364
     * @return the <code>scheme://domain:port</code> part of the initial url of this page.
365
     */
366
    public String getInitialUrlBase() {
367
        return pageUrl.getProtocol() + "://" + pageUrl.getHost() + pageUrl.getPort();
368
    }
369

    
370
    @Override
371
    public boolean equals(Object obj) {
372
        if (PortalPage.class.isAssignableFrom(obj.getClass())) {
373
            PortalPage page = (PortalPage) obj;
374
            return this.getPageURL().toString().equals(page.getPageURL().toString());
375

    
376
        } else {
377
            return false;
378
        }
379
    }
380

    
381

    
382
    /**
383
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
384
     * @param pageType the return type
385
     * @param duration may be null, if this in null <code>waitUnit</code> will be ignored.
386
     * @param waitUnit may be null, is ignored if <code>duration</code> is null defaults to {@link TimeUnit#SECONDS}
387

    
388
     */
389
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> pageType, Long duration, TimeUnit waitUnit) {
390

    
391
        if( pageType.getClass().equals(PortalPage.class) ) {
392
            throw new RuntimeException("Parameter pageType must be a subclass of PortalPage");
393
        }
394
        String targetWindow = null;
395
        List<String> targets = element.getLinkTargets(driver);
396
        if(targets.size() > 0){
397
            targetWindow = targets.get(0);
398
        }
399

    
400
        if(logger.isInfoEnabled() || logger.isDebugEnabled()){
401
            logger.info("clickLink() on " + element.toStringWithLinks());
402
        }
403
        element.getElement().click();
404
        if(targetWindow != null){
405
            driver.switchTo().window(targetWindow);
406
        }
407

    
408
        try {
409
            if(duration != null){
410
                if(waitUnit == null){
411
                    waitUnit = TimeUnit.SECONDS;
412
                }
413
                wait.withTimeout(duration, waitUnit).until(isTrue);
414
            } else {
415
                wait.until(isTrue);
416
            }
417
        } catch (AssertionError timeout){
418
            logger.info("clickLink timed out. Current WindowHandles:" + driver.getWindowHandles());
419
            throw timeout;
420
        }
421

    
422

    
423
        Constructor<T> constructor;
424
        T pageInstance;
425
        try {
426
            constructor = pageType.getConstructor(WebDriver.class, DataPortalContext.class);
427
            pageInstance = constructor.newInstance(driver, context);
428
        } catch (Exception e) {
429
            throw new RuntimeException(e);
430
        }
431
        // take screenshot of new page
432
        takeScreenShot();
433
        return pageInstance;
434
    }
435

    
436

    
437
    /**
438
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
439
     * @param type the return type
440

    
441
     */
442
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> type) {
443
        return clickLink(element, isTrue, type, null, null);
444
    }
445

    
446

    
447
    /**
448
     * replaces all underscores '_' by hyphens '-'
449
     */
450
    protected String normalizeClassAttribute(String featureName) {
451
        return featureName.replace('_', '-');
452
    }
453

    
454
    public File takeScreenShot(){
455

    
456

    
457
        logger.info("Screenshot ...");
458
        File destFile = fileForTestMethod(new File("screenshots"));
459
        if(logger.isDebugEnabled()){
460
            logger.debug("Screenshot destFile" + destFile.getPath().toString());
461
        }
462
        File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
463
        try {
464
            FileUtils.copyFile(scrFile, destFile);
465
            logger.info("Screenshot taken and saved as " + destFile.getAbsolutePath());
466
            return destFile;
467
        } catch (IOException e) {
468
            logger.error("could not copy sceenshot to " + destFile.getAbsolutePath(), e);
469
        }
470

    
471
        return null;
472

    
473
    }
474

    
475
    /**
476
     * Finds the test class and method in the stack trace which is using the
477
     * PortalPage and returns a File object consisting of:
478
     * {@code $targetFolder/$className/$methodName }
479
     *
480
     *
481
     * If no test class is found it will fall back to using
482
     * "noTest" as folder name and a timestamp as filename.
483
     */
484
    private File fileForTestMethod(File targetFolder){
485

    
486
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
487
        for (StackTraceElement stackTraceElement : trace) {
488
            // according to the convention all test class names should end with "Test"
489
            if(logger.isTraceEnabled()){
490
                logger.trace("fileForTestMethod() - " + stackTraceElement.toString());
491
            }
492
            if(stackTraceElement.getClassName().endsWith("Test")){
493
                return uniqueIndexedFile(
494
                            targetFolder.getAbsolutePath() + File.separator + stackTraceElement.getClassName(),
495
                            stackTraceElement.getMethodName(),
496
                            "png");
497

    
498
            }
499
        }
500
        return uniqueIndexedFile(
501
                targetFolder.getAbsolutePath() + File.separator + "noTest",
502
                Long.toString(System.currentTimeMillis()),
503
                "png");
504
    }
505

    
506

    
507
    private File uniqueIndexedFile(String folder, String fileName, String suffix){
508
        File file;
509
        int i = 0;
510
        while(true){
511
            file = new File(folder + File.separator + fileName + "_" + Integer.toString(i++) + "." + suffix);
512
            if(!file.exists()){
513
                return file;
514
            }
515
        }
516
    }
517

    
518

    
519
}
(4-4/9)