Project

General

Profile

Download (17.1 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.PageFactory;
29
import org.openqa.selenium.support.ui.WebDriverWait;
30

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

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

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

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

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

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

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

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

    
61
    protected WebDriver driver;
62

    
63
    protected DataPortalContext context;
64

    
65
    protected final JUnitWebDriverWait wait;
66

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

    
71

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

    
80
    private String initialDrupalPagePath;
81

    
82
    protected URL pageUrl;
83

    
84
    // ==== WebElements === //
85

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

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

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

    
96

    
97
    @FindBy(id="block-cdm-dataportal-2")
98
    @CacheLookup
99
    protected WebElement searchBlockElement;
100

    
101
    @FindBy(id="block-cdm-taxontree-cdm-tree")
102
    @CacheLookup
103
    protected WebElement classificationBrowserBlock;
104

    
105
    @FindBy(className="messages")
106
    @CacheLookup
107
    protected List<WebElement> messages;
108

    
109
    private Boolean isZenTheme;
110

    
111
    public boolean isZenTheme() {
112
        if(isZenTheme == null) {
113
            isZenTheme = driver.getPageSource().contains("themes/zen_dataportal");
114
        }
115
        return isZenTheme.booleanValue();
116
    }
117

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

    
131
        this.driver = driver;
132

    
133
        this.context = context;
134

    
135
        this.wait = new JUnitWebDriverWait(driver, WAIT_SECONDS);
136

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

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

    
141
        // tell browser to navigate to the page
142
        driver.get(pageUrl.toString());
143

    
144
        takeScreenShot();
145

    
146
        // This call sets the WebElement fields.
147
        PageFactory.initElements(driver, this);
148

    
149
        logger.info("loading " + pageUrl);
150

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

    
159
    }
160

    
161
    /**
162
     * Creates a new PortaPage at given URL location. An Exception is thrown if
163
     * this URL is not matching the expected URL for the specific page type.
164
     *
165
     */
166
    public PortalPage(WebDriver driver, DataPortalContext context, URL url) throws Exception {
167

    
168
        this.driver = driver;
169

    
170
        this.context = context;
171

    
172
        this.wait = new JUnitWebDriverWait(driver, 25);
173

    
174
        this.pageUrl = new URL(context.getBaseUri().toString());
175

    
176
        // tell browser to navigate to the given URL
177
        driver.get(url.toString());
178

    
179
        takeScreenShot();
180

    
181
        if(!isOnPage()){
182
            throw new Exception("Not on the expected portal page ( current: " + driver.getCurrentUrl() + ", expected: " +  pageUrl + " )");
183
        }
184

    
185
        this.pageUrl = url;
186

    
187
        logger.info("loading " + pageUrl);
188

    
189
        // This call sets the WebElement fields.
190
        PageFactory.initElements(driver, this);
191

    
192
    }
193

    
194
    /**
195
     * Creates a new PortaPage at the WebDrivers current URL location. An Exception is thrown if
196
     * driver.getCurrentUrl() is not matching the expected URL for the specific page type.
197
     *
198
     */
199
    public PortalPage(WebDriver driver, DataPortalContext context) throws Exception {
200

    
201
        this.driver = driver;
202

    
203
        this.context = context;
204

    
205
        this.wait = new JUnitWebDriverWait(driver, 25);
206

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

    
211
        takeScreenShot();
212

    
213
        if(!isOnPage()){
214
            throw new Exception("Not on the expected portal page ( current: " + driver.getCurrentUrl() + ", expected: " +  pageUrl + " )");
215
        }
216

    
217
        // now set the real URL
218
        this.pageUrl = new URL(driver.getCurrentUrl());
219

    
220
        logger.info("loading " + pageUrl);
221

    
222
        // This call sets the WebElement fields.
223
        PageFactory.initElements(driver, this);
224

    
225
    }
226

    
227

    
228
    protected boolean isOnPage() {
229
        return driver.getCurrentUrl().startsWith(pageUrl.toString());
230
    }
231

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

    
245
    /**
246
     * go back in history
247
     */
248
    public void back() {
249
        driver.navigate().back();
250
    }
251

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

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

    
282
    /**
283
     *
284
     * @return the page title
285
     * @deprecated use {@link WebDriver#getTitle()}
286
     */
287
    @Deprecated
288
    public String getTitle() {
289
        return driver.getTitle();
290
    }
291

    
292
    public List<String> getMessageItems(MessageType messageType){
293
        if(messages != null){
294
            for(WebElement m : messages){
295
                if(m.getAttribute("class").contains(messageType.name())){
296
                    List<WebElement> messageItems = m.findElements(By.cssSelector("ul.messages__list li"));
297
                    if(messageItems.size() == 0 && !m.getText().isEmpty()) {
298
                        // we have only one item which is not shown as list.
299
                        messageItems.add(m);
300

    
301
                    }
302
                    return messageItems.stream().map(mi -> mi.getText()).collect(Collectors.toList());
303
                }
304
            }
305
        }
306
        return new ArrayList<>();
307
    }
308

    
309
    /**
310
     * @return the warning messages from the Drupal message box
311
     */
312
    public List<String> getWarnings() {
313
        return getMessageItems(MessageType.warning);
314
    }
315

    
316
    /**
317
     * @return the error messages from the Drupal message box
318
     */
319
    public List<String> getErrors() {
320
        return getMessageItems(MessageType.error);
321
    }
322

    
323
    public String getAuthorInformationText() {
324

    
325
        WebElement authorInformation = null;
326

    
327
        try {
328
            authorInformation  = node.findElement(By.className("submitted"));
329
        } catch (NoSuchElementException e) {
330
            // IGNORE //
331
        }
332

    
333

    
334
        if(authorInformation != null){
335
            return authorInformation.getText();
336
        } else {
337
            return null;
338
        }
339
    }
340

    
341
    public List<LinkElement> getPrimaryTabs(){
342
        List<LinkElement> tabs = new ArrayList<LinkElement>();
343
        List<WebElement> primaryTabLinks = null;
344
        if(isZenTheme()) {
345
            primaryTabLinks = driver.findElements(By.cssSelector(".tabs-primary li a"));
346
        } else {
347
            // try the old garland theme
348
            primaryTabLinks = driver.findElements(By.cssSelector("#tabs-wrapper ul.primary li a"));
349
        }
350

    
351
        for(WebElement a : primaryTabLinks) {
352
            WebElement renderedLink = a;
353
            if(renderedLink.isDisplayed()){
354
                tabs.add(new LinkElement(renderedLink));
355
            }
356
        }
357

    
358
        return tabs;
359
    }
360

    
361
    /**
362
     * Provides access to the the CDM specific content which is put into the <code>div</code> DOM element which has the class attribute <code>class="... node ..."</code>
363
     * @return
364
     */
365
    public BaseElement getDataPortalContent() {
366
        return new BaseElement(portalContent);
367
    }
368

    
369
    public ClassificationTreeBlock getClassificationTree() {
370
        return new ClassificationTreeBlock(classificationBrowserBlock);
371
    }
372

    
373
    public void hover(WebElement element) {
374
        Actions actions = new Actions(driver);
375
        actions.moveToElement(element, 1, 1).perform();
376
        logger.debug("hovering");
377
    }
378

    
379

    
380
    /**
381
     * @return the current URL string from the {@link WebDriver}
382
     */
383
    public URL getPageURL() {
384
        return pageUrl;
385
    }
386

    
387

    
388
    /**
389
     * @return the <code>scheme://domain:port</code> part of the initial url of this page.
390
     */
391
    public String getInitialUrlBase() {
392
        return pageUrl.getProtocol() + "://" + pageUrl.getHost() + pageUrl.getPort();
393
    }
394

    
395
    @Override
396
    public boolean equals(Object obj) {
397
        if (PortalPage.class.isAssignableFrom(obj.getClass())) {
398
            PortalPage page = (PortalPage) obj;
399
            return this.getPageURL().toString().equals(page.getPageURL().toString());
400

    
401
        } else {
402
            return false;
403
        }
404
    }
405

    
406

    
407
    /**
408
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
409
     * @param pageType the return type
410
     * @param duration may be null, if this in null <code>waitUnit</code> will be ignored.
411
     * @param waitUnit may be null, is ignored if <code>duration</code> is null defaults to {@link TimeUnit#SECONDS}
412

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

    
416
        if( pageType.getClass().equals(PortalPage.class) ) {
417
            throw new RuntimeException("Parameter pageType must be a subclass of PortalPage");
418
        }
419
        String targetWindow = null;
420
        List<String> targets = element.getLinkTargets(driver);
421
        if(targets.size() > 0){
422
            targetWindow = targets.get(0);
423
        }
424

    
425
        if(logger.isInfoEnabled() || logger.isDebugEnabled()){
426
            logger.info("clickLink() on " + element.toStringWithLinks());
427
        }
428
        element.getElement().click();
429
        if(targetWindow != null){
430
            driver.switchTo().window(targetWindow);
431
        }
432

    
433
        try {
434
            if(duration != null){
435
                if(waitUnit == null){
436
                    waitUnit = TimeUnit.SECONDS;
437
                }
438
                wait.withTimeout(duration, waitUnit).until(isTrue);
439
            } else {
440
                wait.until(isTrue);
441
            }
442
        } catch (AssertionError timeout){
443
            logger.info("clickLink timed out. Current WindowHandles:" + driver.getWindowHandles());
444
            throw timeout;
445
        }
446

    
447

    
448
        Constructor<T> constructor;
449
        T pageInstance;
450
        try {
451
            constructor = pageType.getConstructor(WebDriver.class, DataPortalContext.class);
452
            pageInstance = constructor.newInstance(driver, context);
453
        } catch (Exception e) {
454
            throw new RuntimeException(e);
455
        }
456
        // take screenshot of new page
457
        takeScreenShot();
458
        return pageInstance;
459
    }
460

    
461

    
462
    /**
463
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
464
     * @param type the return type
465

    
466
     */
467
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> type) {
468
        return clickLink(element, isTrue, type, null, null);
469
    }
470

    
471

    
472
    /**
473
     * replaces all underscores '_' by hyphens '-'
474
     */
475
    protected String normalizeClassAttribute(String featureName) {
476
        return featureName.replace('_', '-');
477
    }
478

    
479
    public File takeScreenShot(){
480

    
481

    
482
        logger.info("Screenshot ...");
483
        File destFile = fileForTestMethod(new File("screenshots"));
484
        if(logger.isDebugEnabled()){
485
            logger.debug("Screenshot destFile" + destFile.getPath().toString());
486
        }
487
        File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
488
        try {
489
            FileUtils.copyFile(scrFile, destFile);
490
            logger.info("Screenshot taken and saved as " + destFile.getAbsolutePath());
491
            return destFile;
492
        } catch (IOException e) {
493
            logger.error("could not copy sceenshot to " + destFile.getAbsolutePath(), e);
494
        }
495

    
496
        return null;
497

    
498
    }
499

    
500
    /**
501
     * Finds the test class and method in the stack trace which is using the
502
     * PortalPage and returns a File object consisting of:
503
     * {@code $targetFolder/$className/$methodName }
504
     *
505
     *
506
     * If no test class is found it will fall back to using
507
     * "noTest" as folder name and a timestamp as filename.
508
     */
509
    private File fileForTestMethod(File targetFolder){
510

    
511
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
512
        for (StackTraceElement stackTraceElement : trace) {
513
            // according to the convention all test class names should end with "Test"
514
            if(logger.isTraceEnabled()){
515
                logger.trace("fileForTestMethod() - " + stackTraceElement.toString());
516
            }
517
            if(stackTraceElement.getClassName().endsWith("Test")){
518
                return uniqueIndexedFile(
519
                            targetFolder.getAbsolutePath() + File.separator + stackTraceElement.getClassName(),
520
                            stackTraceElement.getMethodName(),
521
                            "png");
522

    
523
            }
524
        }
525
        return uniqueIndexedFile(
526
                targetFolder.getAbsolutePath() + File.separator + "noTest",
527
                Long.toString(System.currentTimeMillis()),
528
                "png");
529
    }
530

    
531

    
532
    private File uniqueIndexedFile(String folder, String fileName, String suffix){
533
        File file;
534
        int i = 0;
535
        while(true){
536
            file = new File(folder + File.separator + fileName + "_" + Integer.toString(i++) + "." + suffix);
537
            if(!file.exists()){
538
                return file;
539
            }
540
        }
541
    }
542

    
543

    
544
}
(4-4/9)