Project

General

Profile

Download (14.8 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.DataPortalSite;
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_BASE = "?q=";
56

    
57
    protected WebDriver driver;
58

    
59
    protected DataPortalContext context;
60

    
61
    protected final JUnitWebDriverWait wait;
62

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

    
67

    
68
    /**
69
     * Implementations of this method will supply the relative
70
     * path to the Drupal page. This path will usally have the form
71
     * <code>cdm_dataportal/{nodetype}</code>. For example the taxon pages all
72
     * have the page base <code>cdm_dataportal/taxon</code>
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
    public PortalPage(WebDriver driver, DataPortalContext context, String pagePathSuffix) throws MalformedURLException {
128

    
129
        this.driver = driver;
130

    
131
        this.context = context;
132

    
133
        this.wait = new JUnitWebDriverWait(driver, WAIT_SECONDS);
134

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

    
137
        this.pageUrl = new URL(context.getBaseUri().toString() + DRUPAL_PAGE_QUERY_BASE + drupalPagePath);
138

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

    
142
        takeScreenShot();
143

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

    
147
        logger.info("loading " + pageUrl);
148

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

    
160
    }
161

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

    
169
        this.driver = driver;
170

    
171
        this.context = context;
172

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

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

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

    
180
        takeScreenShot();
181

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

    
186
        this.pageUrl = url;
187

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

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

    
193
    }
194

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

    
202
        this.driver = driver;
203

    
204
        this.context = context;
205

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

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

    
212
        takeScreenShot();
213

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

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

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

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

    
226
    }
227

    
228

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

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

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

    
253
    public String getDrupalPagePath() {
254
        return drupalPagePath;
255
    }
256

    
257
    /**
258
     *
259
     * @return the page title
260
     * @deprecated use {@link WebDriver#getTitle()}
261
     */
262
    @Deprecated
263
    public String getTitle() {
264
        return driver.getTitle();
265
    }
266

    
267
    /**
268
     * @return the warning messages from the Drupal message box
269
     */
270
    public String getWarnings() {
271
        return null; //TODO unimplemented
272
    }
273

    
274
    /**
275
     * @return the error messages from the Drupal message box
276
     */
277
    public String getErrors() {
278
        return null; //TODO unimplemented
279
    }
280

    
281
    public String getAuthorInformationText() {
282

    
283
        WebElement authorInformation = null;
284

    
285
        try {
286
            authorInformation  = node.findElement(By.className("submitted"));
287
        } catch (NoSuchElementException e) {
288
            // IGNORE //
289
        }
290

    
291

    
292
        if(authorInformation != null){
293
            return authorInformation.getText();
294
        } else {
295
            return null;
296
        }
297
    }
298

    
299
    public List<LinkElement> getPrimaryTabs(){
300
        List<LinkElement> tabs = new ArrayList<LinkElement>();
301
        List<WebElement> links = primaryTabs.findElements(By.tagName("a"));
302
        for(WebElement a : links) {
303
            WebElement renderedLink = a;
304
            if(renderedLink.isDisplayed()){
305
                tabs.add(new LinkElement(renderedLink));
306
            }
307
        }
308

    
309
        return tabs;
310
    }
311

    
312
    public ClassificationTreeBlock getClassificationTree() {
313
        return new ClassificationTreeBlock(classificationBrowserBlock);
314
    }
315

    
316
    public void hover(WebElement element) {
317
        Actions actions = new Actions(driver);
318
        actions.moveToElement(element, 1, 1).perform();
319
        logger.debug("hovering");
320
    }
321

    
322

    
323
    /**
324
     * @return the current URL string from the {@link WebDriver}
325
     */
326
    public URL getPageURL() {
327
        return pageUrl;
328
    }
329

    
330

    
331
    /**
332
     * @return the <code>scheme://domain:port</code> part of the initial url of this page.
333
     */
334
    public String getInitialUrlBase() {
335
        return pageUrl.getProtocol() + "://" + pageUrl.getHost() + pageUrl.getPort();
336
    }
337

    
338
    @Override
339
    public boolean equals(Object obj) {
340
        if (PortalPage.class.isAssignableFrom(obj.getClass())) {
341
            PortalPage page = (PortalPage) obj;
342
            return this.getPageURL().toString().equals(page.getPageURL().toString());
343

    
344
        } else {
345
            return false;
346
        }
347
    }
348

    
349

    
350
    /**
351
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
352
     * @param pageType the return type
353
     * @param duration may be null, if this in null <code>waitUnit</code> will be ignored.
354
     * @param waitUnit may be null, is ignored if <code>duration</code> is null defaults to {@link TimeUnit#SECONDS}
355

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

    
359
        if( pageType.getClass().equals(PortalPage.class) ) {
360
            throw new RuntimeException("Parameter pageType must be a subclass of PortalPage");
361
        }
362
        String targetWindow = null;
363
        List<String> targets = element.getLinkTargets(driver);
364
        if(targets.size() > 0){
365
            targetWindow = targets.get(0);
366
        }
367

    
368
        if(logger.isInfoEnabled() || logger.isDebugEnabled()){
369
            logger.info("clickLink() on " + element.toStringWithLinks());
370
        }
371
        element.getElement().click();
372
        if(targetWindow != null){
373
            driver.switchTo().window(targetWindow);
374
        }
375

    
376
        try {
377
            if(duration != null){
378
                if(waitUnit == null){
379
                    waitUnit = TimeUnit.SECONDS;
380
                }
381
                wait.withTimeout(duration, waitUnit).until(isTrue);
382
            } else {
383
                wait.until(isTrue);
384
            }
385
        } catch (AssertionError timeout){
386
            logger.info("clickLink timed out. Current WindowHandles:" + driver.getWindowHandles());
387
            throw timeout;
388
        }
389

    
390

    
391
        Constructor<T> constructor;
392
        T pageInstance;
393
        try {
394
            constructor = pageType.getConstructor(WebDriver.class, DataPortalSite.class);
395
            pageInstance = constructor.newInstance(driver, context);
396
        } catch (Exception e) {
397
            throw new RuntimeException(e);
398
        }
399
        // take screenshot of new page
400
        takeScreenShot();
401
        return pageInstance;
402
    }
403

    
404

    
405
    /**
406
     * @param isTrue see {@link org.openqa.selenium.support.ui.FluentWait#until(Function)}
407
     * @param type the return type
408

    
409
     */
410
    public <T extends PortalPage> T clickLink(BaseElement element, Function<? super WebDriver, Boolean> isTrue, Class<T> type) {
411
        return clickLink(element, isTrue, type, null, null);
412
    }
413

    
414

    
415
    /**
416
     * replaces all underscores '_' by hyphens '-'
417
     */
418
    protected String normalizeClassAttribute(String featureName) {
419
        return featureName.replace('_', '-');
420
    }
421

    
422
    public File takeScreenShot(){
423

    
424

    
425
        logger.info("Screenshot ...");
426
        File destFile = fileForTestMethod(new File("screenshots"));
427
        if(logger.isDebugEnabled()){
428
            logger.debug("Screenshot destFile" + destFile.getPath().toString());
429
        }
430
        File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
431
        try {
432
            FileUtils.copyFile(scrFile, destFile);
433
            logger.info("Screenshot taken and saved as " + destFile.getAbsolutePath());
434
            return destFile;
435
        } catch (IOException e) {
436
            logger.error("could not copy sceenshot to " + destFile.getAbsolutePath(), e);
437
        }
438

    
439
        return null;
440

    
441
    }
442

    
443
    /**
444
     * Finds the test class and method in the stack trace which is using the
445
     * PortalPage and returns a File object consisting of:
446
     * {@code $targetFolder/$className/$methodName }
447
     *
448
     *
449
     * If no test class is found it will fall back to using
450
     * "noTest" as folder name and a timestamp as filename.
451
     */
452
    private File fileForTestMethod(File targetFolder){
453

    
454
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
455
        for (StackTraceElement stackTraceElement : trace) {
456
            // according to the convention all test class names should end with "Test"
457
            if(logger.isTraceEnabled()){
458
                logger.trace("fileForTestMethod() - " + stackTraceElement.toString());
459
            }
460
            if(stackTraceElement.getClassName().endsWith("Test")){
461
                return uniqueIndexedFile(
462
                            targetFolder.getAbsolutePath() + File.separator + stackTraceElement.getClassName(),
463
                            stackTraceElement.getMethodName(),
464
                            "png");
465

    
466
            }
467
        }
468
        return uniqueIndexedFile(
469
                targetFolder.getAbsolutePath() + File.separator + "noTest",
470
                Long.toString(System.currentTimeMillis()),
471
                "png");
472
    }
473

    
474

    
475
    private File uniqueIndexedFile(String folder, String fileName, String suffix){
476
        File file;
477
        int i = 0;
478
        while(true){
479
            file = new File(folder + File.separator + fileName + "_" + Integer.toString(i++) + "." + suffix);
480
            if(!file.exists()){
481
                return file;
482
            }
483
        }
484
    }
485

    
486

    
487
}
(3-3/6)