Project

General

Profile

Download (15.6 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

    
16
import org.apache.commons.io.FileUtils;
17
import org.apache.log4j.Logger;
18
import org.openqa.selenium.By;
19
import org.openqa.selenium.NoSuchElementException;
20
import org.openqa.selenium.OutputType;
21
import org.openqa.selenium.TakesScreenshot;
22
import org.openqa.selenium.WebDriver;
23
import org.openqa.selenium.WebElement;
24
import org.openqa.selenium.interactions.Actions;
25
import org.openqa.selenium.support.CacheLookup;
26
import org.openqa.selenium.support.FindBy;
27
import org.openqa.selenium.support.FindBys;
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
    @FindBys({@FindBy(id="tabs-wrapper"), @FindBy(className="primary")})
97
    @CacheLookup
98
    protected WebElement primaryTabs;
99

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

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

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

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

    
125
        this.driver = driver;
126

    
127
        this.context = context;
128

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

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

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

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

    
138
        takeScreenShot();
139

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

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

    
145
        try {
146
            assertTrue("The page must not show an error box", getErrors() == null);
147
        } catch (NoSuchElementException e) {
148
            //IGNORE since this is expected!
149
        }
150

    
151
    }
152

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

    
160
        this.driver = driver;
161

    
162
        this.context = context;
163

    
164
        this.wait = new JUnitWebDriverWait(driver, 25);
165

    
166
        this.pageUrl = new URL(context.getBaseUri().toString());
167

    
168
        // tell browser to navigate to the given URL
169
        driver.get(url.toString());
170

    
171
        takeScreenShot();
172

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

    
177
        this.pageUrl = url;
178

    
179
        logger.info("loading " + pageUrl);
180

    
181
        // This call sets the WebElement fields.
182
        PageFactory.initElements(driver, this);
183

    
184
    }
185

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

    
193
        this.driver = driver;
194

    
195
        this.context = context;
196

    
197
        this.wait = new JUnitWebDriverWait(driver, 25);
198

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

    
203
        takeScreenShot();
204

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

    
209
        // now set the real URL
210
        this.pageUrl = new URL(driver.getCurrentUrl());
211

    
212
        logger.info("loading " + pageUrl);
213

    
214
        // This call sets the WebElement fields.
215
        PageFactory.initElements(driver, this);
216

    
217
    }
218

    
219

    
220
    protected boolean isOnPage() {
221
        return driver.getCurrentUrl().startsWith(pageUrl.toString());
222
    }
223

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

    
237
    /**
238
     * go back in history
239
     */
240
    public void back() {
241
        driver.navigate().back();
242
    }
243

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

    
260
    /**
261
     * Returns the the page path with which the page has initially been loaded.
262
     * Due to redirects the actual page path which can be retrieved by
263
     * {@link #getDrupalPagePath()} might be different
264
     *
265
     */
266
    public String getInitialDrupalPagePath() {
267
        return initialDrupalPagePath;
268
    }
269

    
270
    /**
271
     *
272
     * @return the page title
273
     * @deprecated use {@link WebDriver#getTitle()}
274
     */
275
    @Deprecated
276
    public String getTitle() {
277
        return driver.getTitle();
278
    }
279

    
280
    public String getMessages(MessageType messageType){
281
        if(messages != null){
282
            for(WebElement m : messages){
283
                if(m.getAttribute("class").contains(messageType.name())){
284
                    return m.getText();
285
                }
286
            }
287
        }
288
        return null;
289
    }
290

    
291
    /**
292
     * @return the warning messages from the Drupal message box
293
     */
294
    public String getWarnings() {
295
        return getMessages(MessageType.warning);
296
    }
297

    
298
    /**
299
     * @return the error messages from the Drupal message box
300
     */
301
    public String getErrors() {
302
        return getMessages(MessageType.error);
303
    }
304

    
305
    public String getAuthorInformationText() {
306

    
307
        WebElement authorInformation = null;
308

    
309
        try {
310
            authorInformation  = node.findElement(By.className("submitted"));
311
        } catch (NoSuchElementException e) {
312
            // IGNORE //
313
        }
314

    
315

    
316
        if(authorInformation != null){
317
            return authorInformation.getText();
318
        } else {
319
            return null;
320
        }
321
    }
322

    
323
    public List<LinkElement> getPrimaryTabs(){
324
        List<LinkElement> tabs = new ArrayList<LinkElement>();
325
        List<WebElement> links = primaryTabs.findElements(By.tagName("a"));
326
        for(WebElement a : links) {
327
            WebElement renderedLink = a;
328
            if(renderedLink.isDisplayed()){
329
                tabs.add(new LinkElement(renderedLink));
330
            }
331
        }
332

    
333
        return tabs;
334
    }
335

    
336
    public ClassificationTreeBlock getClassificationTree() {
337
        return new ClassificationTreeBlock(classificationBrowserBlock);
338
    }
339

    
340
    public void hover(WebElement element) {
341
        Actions actions = new Actions(driver);
342
        actions.moveToElement(element, 1, 1).perform();
343
        logger.debug("hovering");
344
    }
345

    
346

    
347
    /**
348
     * @return the current URL string from the {@link WebDriver}
349
     */
350
    public URL getPageURL() {
351
        return pageUrl;
352
    }
353

    
354

    
355
    /**
356
     * @return the <code>scheme://domain:port</code> part of the initial url of this page.
357
     */
358
    public String getInitialUrlBase() {
359
        return pageUrl.getProtocol() + "://" + pageUrl.getHost() + pageUrl.getPort();
360
    }
361

    
362
    @Override
363
    public boolean equals(Object obj) {
364
        if (PortalPage.class.isAssignableFrom(obj.getClass())) {
365
            PortalPage page = (PortalPage) obj;
366
            return this.getPageURL().toString().equals(page.getPageURL().toString());
367

    
368
        } else {
369
            return false;
370
        }
371
    }
372

    
373

    
374
    /**
375
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
376
     * @param pageType the return type
377
     * @param duration may be null, if this in null <code>waitUnit</code> will be ignored.
378
     * @param waitUnit may be null, is ignored if <code>duration</code> is null defaults to {@link TimeUnit#SECONDS}
379

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

    
383
        if( pageType.getClass().equals(PortalPage.class) ) {
384
            throw new RuntimeException("Parameter pageType must be a subclass of PortalPage");
385
        }
386
        String targetWindow = null;
387
        List<String> targets = element.getLinkTargets(driver);
388
        if(targets.size() > 0){
389
            targetWindow = targets.get(0);
390
        }
391

    
392
        if(logger.isInfoEnabled() || logger.isDebugEnabled()){
393
            logger.info("clickLink() on " + element.toStringWithLinks());
394
        }
395
        element.getElement().click();
396
        if(targetWindow != null){
397
            driver.switchTo().window(targetWindow);
398
        }
399

    
400
        try {
401
            if(duration != null){
402
                if(waitUnit == null){
403
                    waitUnit = TimeUnit.SECONDS;
404
                }
405
                wait.withTimeout(duration, waitUnit).until(isTrue);
406
            } else {
407
                wait.until(isTrue);
408
            }
409
        } catch (AssertionError timeout){
410
            logger.info("clickLink timed out. Current WindowHandles:" + driver.getWindowHandles());
411
            throw timeout;
412
        }
413

    
414

    
415
        Constructor<T> constructor;
416
        T pageInstance;
417
        try {
418
            constructor = pageType.getConstructor(WebDriver.class, DataPortalContext.class);
419
            pageInstance = constructor.newInstance(driver, context);
420
        } catch (Exception e) {
421
            throw new RuntimeException(e);
422
        }
423
        // take screenshot of new page
424
        takeScreenShot();
425
        return pageInstance;
426
    }
427

    
428

    
429
    /**
430
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
431
     * @param type the return type
432

    
433
     */
434
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> type) {
435
        return clickLink(element, isTrue, type, null, null);
436
    }
437

    
438

    
439
    /**
440
     * replaces all underscores '_' by hyphens '-'
441
     */
442
    protected String normalizeClassAttribute(String featureName) {
443
        return featureName.replace('_', '-');
444
    }
445

    
446
    public File takeScreenShot(){
447

    
448

    
449
        logger.info("Screenshot ...");
450
        File destFile = fileForTestMethod(new File("screenshots"));
451
        if(logger.isDebugEnabled()){
452
            logger.debug("Screenshot destFile" + destFile.getPath().toString());
453
        }
454
        File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
455
        try {
456
            FileUtils.copyFile(scrFile, destFile);
457
            logger.info("Screenshot taken and saved as " + destFile.getAbsolutePath());
458
            return destFile;
459
        } catch (IOException e) {
460
            logger.error("could not copy sceenshot to " + destFile.getAbsolutePath(), e);
461
        }
462

    
463
        return null;
464

    
465
    }
466

    
467
    /**
468
     * Finds the test class and method in the stack trace which is using the
469
     * PortalPage and returns a File object consisting of:
470
     * {@code $targetFolder/$className/$methodName }
471
     *
472
     *
473
     * If no test class is found it will fall back to using
474
     * "noTest" as folder name and a timestamp as filename.
475
     */
476
    private File fileForTestMethod(File targetFolder){
477

    
478
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
479
        for (StackTraceElement stackTraceElement : trace) {
480
            // according to the convention all test class names should end with "Test"
481
            if(logger.isTraceEnabled()){
482
                logger.trace("fileForTestMethod() - " + stackTraceElement.toString());
483
            }
484
            if(stackTraceElement.getClassName().endsWith("Test")){
485
                return uniqueIndexedFile(
486
                            targetFolder.getAbsolutePath() + File.separator + stackTraceElement.getClassName(),
487
                            stackTraceElement.getMethodName(),
488
                            "png");
489

    
490
            }
491
        }
492
        return uniqueIndexedFile(
493
                targetFolder.getAbsolutePath() + File.separator + "noTest",
494
                Long.toString(System.currentTimeMillis()),
495
                "png");
496
    }
497

    
498

    
499
    private File uniqueIndexedFile(String folder, String fileName, String suffix){
500
        File file;
501
        int i = 0;
502
        while(true){
503
            file = new File(folder + File.separator + fileName + "_" + Integer.toString(i++) + "." + suffix);
504
            if(!file.exists()){
505
                return file;
506
            }
507
        }
508
    }
509

    
510

    
511
}
(3-3/7)