Project

General

Profile

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

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

    
6
import java.io.File;
7
import java.io.IOException;
8
import java.lang.reflect.Constructor;
9
import java.net.MalformedURLException;
10
import java.net.URL;
11
import java.util.ArrayList;
12
import java.util.List;
13
import java.util.concurrent.TimeUnit;
14

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

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

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

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

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

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

    
54
    protected final static String DRUPAL_PAGE_QUERY_BASE = "?q=";
55

    
56
    protected WebDriver driver;
57

    
58
    protected DataPortalContext context;
59

    
60
    protected final JUnitWebDriverWait wait;
61

    
62
    public WebDriverWait getWait() {
63
        return wait;
64
    }
65

    
66

    
67
    /**
68
     * Implementations of this method will supply the relative
69
     * path to the Drupal page. This path will usally have the form
70
     * <code>cdm_dataportal/{nodetype}</code>. For example the taxon pages all
71
     * have the page base <code>cdm_dataportal/taxon</code>
72
     * @return
73
     */
74
    protected abstract String getDrupalPageBase();
75

    
76
    private String drupalPagePath;
77

    
78
    protected URL pageUrl;
79

    
80
    // ==== WebElements === //
81

    
82
    @FindBy(className="node")
83
    protected WebElement portalContent;
84

    
85
//    @FindBy(tagName="title")
86
//    @CacheLookup
87
//    protected WebElement title;
88

    
89
    @FindBy(className="node")
90
    protected WebElement node;
91

    
92
    @FindBys({@FindBy(id="tabs-wrapper"), @FindBy(className="primary")})
93
    @CacheLookup
94
    protected WebElement primaryTabs;
95

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

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

    
104
//    @FindBys({
105
//        @FindBy(className="messages"),
106
//        @FindBy(className="error")}
107
//        )
108
    @FindBy(className="messages")
109
    @CacheLookup
110
    protected WebElement messagesErrorCichorieaeTheme;
111

    
112
    @FindBy(className="messages_error")
113
    @CacheLookup
114
    protected WebElement messagesErrorOtherThemes;
115

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

    
134
        this.driver = driver;
135

    
136
        this.context = context;
137

    
138
        this.wait = new JUnitWebDriverWait(driver, WAIT_SECONDS);
139

    
140
        this.drupalPagePath = getDrupalPageBase() + (pagePathSuffix != null ? "/" + pagePathSuffix: "");
141

    
142
        this.pageUrl = new URL(context.getBaseUri().toString() + DRUPAL_PAGE_QUERY_BASE + drupalPagePath);
143

    
144
        // tell browser to navigate to the page
145
        driver.get(pageUrl.toString());
146

    
147
        takeScreenShot();
148

    
149
        // This call sets the WebElement fields.
150
        PageFactory.initElements(driver, this);
151

    
152
        logger.info("loading " + pageUrl);
153

    
154
        try {
155
            assertTrue("The page must not show an error box", !messagesErrorCichorieaeTheme.getAttribute("class").contains("error") && messagesErrorCichorieaeTheme.getText() == null);
156
        } catch (NoSuchElementException e) {
157
            //IGNORE since this is expected!
158
        }
159
        try {
160
            assertNull("The page must not show an error box", messagesErrorOtherThemes.getText());
161
        } catch (NoSuchElementException e) {
162
            //IGNORE since this is expected!
163
        }
164

    
165
    }
166

    
167
    /**
168
     * Creates a new PortaPage at given URL location. An Exception is thrown if
169
     * this URL is not matching the expected URL for the specific page type.
170
     *
171
     * @param driver
172
     * @param context
173
     * @param url
174
     * @throws Exception
175
     */
176
    public PortalPage(WebDriver driver, DataPortalContext context, URL url) throws Exception {
177

    
178
        this.driver = driver;
179

    
180
        this.context = context;
181

    
182
        this.wait = new JUnitWebDriverWait(driver, 25);
183

    
184
        this.pageUrl = new URL(context.getBaseUri().toString());
185

    
186
        // tell browser to navigate to the given URL
187
        driver.get(url.toString());
188

    
189
        takeScreenShot();
190

    
191
        if(!isOnPage()){
192
            throw new Exception("Not on the expected portal page ( current: " + driver.getCurrentUrl() + ", expected: " +  pageUrl + " )");
193
        }
194

    
195
        this.pageUrl = url;
196

    
197
        logger.info("loading " + pageUrl);
198

    
199
        // This call sets the WebElement fields.
200
        PageFactory.initElements(driver, this);
201

    
202
    }
203

    
204
    /**
205
     * Creates a new PortaPage at the WebDrivers current URL location. An Exception is thrown if
206
     * driver.getCurrentUrl() is not matching the expected URL for the specific page type.
207
     *
208
     * @param driver
209
     * @param context
210
     * @throws Exception
211
     */
212
    public PortalPage(WebDriver driver, DataPortalContext context) throws Exception {
213

    
214
        this.driver = driver;
215

    
216
        this.context = context;
217

    
218
        this.wait = new JUnitWebDriverWait(driver, 25);
219

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

    
224
        takeScreenShot();
225

    
226
        if(!isOnPage()){
227
            throw new Exception("Not on the expected portal page ( current: " + driver.getCurrentUrl() + ", expected: " +  pageUrl + " )");
228
        }
229

    
230
        // now set the real URL
231
        this.pageUrl = new URL(driver.getCurrentUrl());
232

    
233
        logger.info("loading " + pageUrl);
234

    
235
        // This call sets the WebElement fields.
236
        PageFactory.initElements(driver, this);
237

    
238
    }
239

    
240
    /**
241
     * @return
242
     */
243
    protected boolean isOnPage() {
244
        return driver.getCurrentUrl().startsWith(pageUrl.toString());
245
    }
246

    
247
    /**
248
     * navigate and reload the page if not jet there
249
     */
250
    public void get() {
251
        if(!driver.getCurrentUrl().equals(pageUrl.toString())){
252
            driver.get(pageUrl.toString());
253
            wait.until(new UrlLoaded(pageUrl.toString()));
254
            // take screenshot of new page
255
            takeScreenShot();
256
            PageFactory.initElements(driver, this);
257
        }
258
    }
259

    
260
    /**
261
     * go back in history
262
     */
263
    public void back() {
264
        driver.navigate().back();
265
    }
266

    
267
    public String getDrupalPagePath() {
268
        return drupalPagePath;
269
    }
270

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

    
281
    /**
282
     * returns the warning messages from the Drupal message box
283
     * @return
284
     */
285
    public String getWarnings() {
286
        return null; //TODO unimplemented
287
    }
288

    
289
    /**
290
     * returns the error messages from the Drupal message box
291
     * @return
292
     */
293
    public String getErrors() {
294
        return null; //TODO unimplemented
295
    }
296

    
297
    public String getAuthorInformationText() {
298

    
299
        WebElement authorInformation = null;
300

    
301
        try {
302
            authorInformation  = node.findElement(By.className("submitted"));
303
        } catch (NoSuchElementException e) {
304
            // IGNORE //
305
        }
306

    
307

    
308
        if(authorInformation != null){
309
            return authorInformation.getText();
310
        } else {
311
            return null;
312
        }
313
    }
314

    
315
    public List<LinkElement> getPrimaryTabs(){
316
        List<LinkElement> tabs = new ArrayList<LinkElement>();
317
        List<WebElement> links = primaryTabs.findElements(By.tagName("a"));
318
        for(WebElement a : links) {
319
            WebElement renderedLink = a;
320
            if(renderedLink.isDisplayed()){
321
                tabs.add(new LinkElement(renderedLink));
322
            }
323
        }
324

    
325
        return tabs;
326
    }
327

    
328
    public ClassificationTreeBlock getClassificationTree() {
329
        return new ClassificationTreeBlock(classificationBrowserBlock);
330
    }
331

    
332
    public void hover(WebElement element) {
333
        Actions actions = new Actions(driver);
334
        actions.moveToElement(element, 1, 1).perform();
335
        logger.debug("hovering");
336
    }
337

    
338

    
339
    /**
340
     * Returns the current URL string from the {@link WebDriver}
341
     * @return
342
     */
343
    public URL getPageURL() {
344
        return pageUrl;
345
    }
346

    
347

    
348
    /**
349
     * return the <code>scheme://domain:port</code> part of the initial url of this page.
350
     * @return
351
     */
352
    public String getInitialUrlBase() {
353
        return pageUrl.getProtocol() + "://" + pageUrl.getHost() + pageUrl.getPort();
354
    }
355

    
356
    @Override
357
    public boolean equals(Object obj) {
358
        if (PortalPage.class.isAssignableFrom(obj.getClass())) {
359
            PortalPage page = (PortalPage) obj;
360
            return this.getPageURL().toString().equals(page.getPageURL().toString());
361

    
362
        } else {
363
            return false;
364
        }
365
    }
366

    
367

    
368
    /**
369
     * @param <T>
370
     * @param link the link to click
371
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
372
     * @param pageType the return type
373
     * @param duration may be null, if this in null <code>waitUnit</code> will be ignored.
374
     * @param waitUnit may be null, is ignored if <code>duration</code> is null defaults to {@link TimeUnit.SECONDS}
375
     * @return
376
     * @throws SecurityException
377
     */
378
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> pageType, Long duration, TimeUnit waitUnit) {
379

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

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

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

    
411

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

    
425

    
426
    /**
427
     * @param <T>
428
     * @param link the link to click
429
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
430
     * @param type the return type
431
     * @return
432
     */
433
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> type) {
434
        return clickLink(element, isTrue, type, null, null);
435
    }
436

    
437

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

    
448
    public File takeScreenShot(){
449

    
450

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

    
465
        return null;
466

    
467
    }
468

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

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

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

    
501

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

    
513

    
514
}
(3-3/6)