michael@0: package org.mozilla.gecko.tests; michael@0: michael@0: import java.io.File; michael@0: import java.io.FileReader; michael@0: import java.io.FileWriter; michael@0: import java.io.IOException; michael@0: michael@0: import org.json.JSONArray; michael@0: import org.json.JSONException; michael@0: import org.json.JSONObject; michael@0: import org.mozilla.gecko.Actions; michael@0: import org.mozilla.gecko.Assert; michael@0: import org.mozilla.gecko.FennecMochitestAssert; michael@0: michael@0: public abstract class SessionTest extends BaseTest { michael@0: protected Navigation mNavigation; michael@0: michael@0: @Override michael@0: public void setUp() throws Exception { michael@0: super.setUp(); michael@0: michael@0: mNavigation = new Navigation(mDevice); michael@0: } michael@0: michael@0: /** michael@0: * A generic session object representing a collection of items that has a michael@0: * selected index. michael@0: */ michael@0: protected abstract class SessionObject { michael@0: private final int mIndex; michael@0: private final T[] mItems; michael@0: michael@0: public SessionObject(int index, T... items) { michael@0: mIndex = index; michael@0: mItems = items; michael@0: } michael@0: michael@0: public int getIndex() { michael@0: return mIndex; michael@0: } michael@0: michael@0: public T[] getItems() { michael@0: return mItems; michael@0: } michael@0: } michael@0: michael@0: protected class PageInfo { michael@0: private String url; michael@0: private String title; michael@0: michael@0: public PageInfo(String key) { michael@0: if (key.startsWith("about:")) { michael@0: url = key; michael@0: } else { michael@0: url = getPage(key); michael@0: } michael@0: title = key; michael@0: } michael@0: } michael@0: michael@0: protected class SessionTab extends SessionObject { michael@0: public SessionTab(int index, PageInfo... items) { michael@0: super(index, items); michael@0: } michael@0: } michael@0: michael@0: protected class Session extends SessionObject { michael@0: public Session(int index, SessionTab... items) { michael@0: super(index, items); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Walker for visiting items in a browser-like navigation order. michael@0: */ michael@0: protected abstract class NavigationWalker { michael@0: private final T[] mItems; michael@0: private final int mIndex; michael@0: michael@0: public NavigationWalker(SessionObject obj) { michael@0: mItems = obj.getItems(); michael@0: mIndex = obj.getIndex(); michael@0: } michael@0: michael@0: /** michael@0: * Walks over the list of items, calling the onItem() callback for each. michael@0: * michael@0: * The selected item is the first item visited. Each item after the michael@0: * selected item is then visited in ascending index order. Finally, the michael@0: * list is iterated in reverse, and each item before the selected item michael@0: * is visited in descending index order. michael@0: */ michael@0: public void walk() { michael@0: onItem(mItems[mIndex], mIndex); michael@0: for (int i = mIndex + 1; i < mItems.length; i++) { michael@0: goForward(); michael@0: onItem(mItems[i], i); michael@0: } michael@0: if (mIndex > 0) { michael@0: for (int i = mItems.length - 2; i >= 0; i--) { michael@0: goBack(); michael@0: if (i < mIndex) { michael@0: onItem(mItems[i], i); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Callback when an item is visited during a walk. michael@0: * michael@0: * Only one callback is executed per item. michael@0: */ michael@0: public abstract void onItem(T item, int currentIndex); michael@0: michael@0: /** michael@0: * Callback executed for each back step of the walk. michael@0: */ michael@0: public void goBack() {} michael@0: michael@0: /** michael@0: * Callback executed for each forward step of the walk. michael@0: */ michael@0: public void goForward() {} michael@0: } michael@0: michael@0: /** michael@0: * Loads a set of tabs in the browser specified by the given session. michael@0: * michael@0: * @param session Session to load michael@0: */ michael@0: protected void loadSessionTabs(Session session) { michael@0: // Verify initial about:home tab michael@0: verifyTabCount(1); michael@0: verifyUrl("about:home"); michael@0: michael@0: SessionTab[] tabs = session.getItems(); michael@0: for (int i = 0; i < tabs.length; i++) { michael@0: final SessionTab tab = tabs[i]; michael@0: final PageInfo[] pages = tab.getItems(); michael@0: michael@0: // New tabs always start with about:home, so make sure about:home michael@0: // is always the first entry. michael@0: mAsserter.is(pages[0].url, "about:home", "first page in tab is about:home"); michael@0: michael@0: // If this is the first tab, the tab already exists, so no need to michael@0: // create a new one. Otherwise, create a new tab if we're loading michael@0: // the first the first page in the set. michael@0: if (i > 0) { michael@0: addTab(); michael@0: } michael@0: michael@0: for (int j = 1; j < pages.length; j++) { michael@0: Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); michael@0: michael@0: loadUrl(pages[j].url); michael@0: michael@0: pageShowExpecter.blockForEvent(); michael@0: pageShowExpecter.unregisterListener(); michael@0: } michael@0: michael@0: final int index = tab.getIndex(); michael@0: for (int j = pages.length - 1; j > index; j--) { michael@0: mNavigation.back(); michael@0: } michael@0: } michael@0: michael@0: selectTabAt(session.getIndex()); michael@0: } michael@0: michael@0: /** michael@0: * Verifies that the set of open tabs matches the given session. michael@0: * michael@0: * @param session Session to verify michael@0: */ michael@0: protected void verifySessionTabs(Session session) { michael@0: verifyTabCount(session.getItems().length); michael@0: michael@0: (new NavigationWalker(session) { michael@0: boolean mFirstTabVisited; michael@0: michael@0: @Override michael@0: public void onItem(SessionTab tab, int currentIndex) { michael@0: // The first tab to check should already be selected at startup michael@0: if (mFirstTabVisited) { michael@0: selectTabAt(currentIndex); michael@0: } else { michael@0: mFirstTabVisited = true; michael@0: } michael@0: michael@0: (new NavigationWalker(tab) { michael@0: @Override michael@0: public void onItem(PageInfo page, int currentIndex) { michael@0: if (page.url.equals("about:home")) { michael@0: waitForText("Enter Search or Address"); michael@0: verifyUrl(page.url); michael@0: } else { michael@0: waitForText(page.title); michael@0: verifyPageTitle(page.title); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void goBack() { michael@0: mNavigation.back(); michael@0: } michael@0: michael@0: @Override michael@0: public void goForward() { michael@0: mNavigation.forward(); michael@0: } michael@0: }).walk(); michael@0: } michael@0: }).walk(); michael@0: } michael@0: michael@0: /** michael@0: * Gets session restore JSON corresponding to the open session. michael@0: * michael@0: * The JSON format follows the format used in Gecko for session restore and michael@0: * should be interchangeable with the Gecko's generated sessionstore.js. michael@0: * michael@0: * @param session Session to serialize michael@0: * @return JSON string of session michael@0: */ michael@0: protected String buildSessionJSON(Session session) { michael@0: final SessionTab[] sessionTabs = session.getItems(); michael@0: String sessionString = null; michael@0: michael@0: try { michael@0: final JSONArray tabs = new JSONArray(); michael@0: michael@0: for (int i = 0; i < sessionTabs.length; i++) { michael@0: final JSONObject tab = new JSONObject(); michael@0: final JSONArray entries = new JSONArray(); michael@0: final SessionTab sessionTab = sessionTabs[i]; michael@0: final PageInfo[] pages = sessionTab.getItems(); michael@0: michael@0: for (int j = 0; j < pages.length; j++) { michael@0: final PageInfo page = pages[j]; michael@0: final JSONObject entry = new JSONObject(); michael@0: entry.put("url", page.url); michael@0: entry.put("title", page.title); michael@0: entries.put(entry); michael@0: } michael@0: michael@0: tab.put("entries", entries); michael@0: tab.put("index", sessionTab.getIndex() + 1); michael@0: tabs.put(tab); michael@0: } michael@0: michael@0: JSONObject window = new JSONObject(); michael@0: window.put("tabs", tabs); michael@0: window.put("selected", session.getIndex() + 1); michael@0: sessionString = new JSONObject().put("windows", new JSONArray().put(window)).toString(); michael@0: } catch (JSONException e) { michael@0: mAsserter.ok(false, "JSON exception", getStackTraceString(e)); michael@0: } michael@0: michael@0: return sessionString; michael@0: } michael@0: michael@0: /** michael@0: * @see SessionTest#verifySessionJSON(Session, String, Assert) michael@0: */ michael@0: protected void verifySessionJSON(Session session, String sessionString) { michael@0: verifySessionJSON(session, sessionString, mAsserter); michael@0: } michael@0: michael@0: /** michael@0: * Verifies a session JSON string against the given session. michael@0: * michael@0: * @param session Session to verify against michael@0: * @param sessionString JSON string to verify michael@0: * @param asserter Assert class to use during verification michael@0: */ michael@0: protected void verifySessionJSON(Session session, String sessionString, Assert asserter) { michael@0: final SessionTab[] sessionTabs = session.getItems(); michael@0: michael@0: try { michael@0: final JSONObject window = new JSONObject(sessionString).getJSONArray("windows").getJSONObject(0); michael@0: final JSONArray tabs = window.getJSONArray("tabs"); michael@0: final int optSelected = window.optInt("selected", -1); michael@0: michael@0: asserter.is(optSelected, session.getIndex() + 1, "selected tab matches"); michael@0: michael@0: for (int i = 0; i < tabs.length(); i++) { michael@0: final JSONObject tab = tabs.getJSONObject(i); michael@0: final int index = tab.getInt("index"); michael@0: final JSONArray entries = tab.getJSONArray("entries"); michael@0: final SessionTab sessionTab = sessionTabs[i]; michael@0: final PageInfo[] pages = sessionTab.getItems(); michael@0: michael@0: asserter.is(index, sessionTab.getIndex() + 1, "selected page index matches"); michael@0: michael@0: for (int j = 0; j < entries.length(); j++) { michael@0: final JSONObject entry = entries.getJSONObject(j); michael@0: final String url = entry.getString("url"); michael@0: final String title = entry.optString("title"); michael@0: final PageInfo page = pages[j]; michael@0: michael@0: asserter.is(url, page.url, "URL in JSON matches session URL"); michael@0: if (!page.url.startsWith("about:")) { michael@0: asserter.is(title, page.title, "title in JSON matches session title"); michael@0: } michael@0: } michael@0: } michael@0: } catch (JSONException e) { michael@0: asserter.ok(false, "JSON exception", getStackTraceString(e)); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Exception thrown by NonFatalAsserter for assertion failures. michael@0: */ michael@0: public static class AssertException extends RuntimeException { michael@0: public AssertException(String msg) { michael@0: super(msg); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Asserter that throws an AssertException on failure instead of aborting michael@0: * the test. michael@0: * michael@0: * This can be used in methods called via waitForCondition() where an assertion michael@0: * might not immediately succeed. michael@0: */ michael@0: public class NonFatalAsserter extends FennecMochitestAssert { michael@0: @Override michael@0: public void ok(boolean condition, String name, String diag) { michael@0: if (!condition) { michael@0: String details = (diag == null ? "" : " | " + diag); michael@0: throw new AssertException("Assertion failed: " + name + details); michael@0: } michael@0: mAsserter.ok(condition, name, diag); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Gets a URL for a dynamically-generated page. michael@0: * michael@0: * The page will have a URL unique to the given ID, and the page's title michael@0: * will match the given ID. michael@0: * michael@0: * @param id ID used to generate page URL michael@0: * @return URL of the page michael@0: */ michael@0: protected String getPage(String id) { michael@0: return getAbsoluteUrl("/robocop/robocop_dynamic.sjs?id=" + id); michael@0: } michael@0: michael@0: protected String readProfileFile(String filename) { michael@0: try { michael@0: return readFile(new File(mProfile, filename)); michael@0: } catch (IOException e) { michael@0: mAsserter.ok(false, "Error reading" + filename, getStackTraceString(e)); michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: protected void writeProfileFile(String filename, String data) { michael@0: try { michael@0: writeFile(new File(mProfile, filename), data); michael@0: } catch (IOException e) { michael@0: mAsserter.ok(false, "Error writing to " + filename, getStackTraceString(e)); michael@0: } michael@0: } michael@0: michael@0: private String readFile(File target) throws IOException { michael@0: if (!target.exists()) { michael@0: return null; michael@0: } michael@0: michael@0: FileReader fr = new FileReader(target); michael@0: try { michael@0: StringBuffer sb = new StringBuffer(); michael@0: char[] buf = new char[8192]; michael@0: int read = fr.read(buf); michael@0: while (read >= 0) { michael@0: sb.append(buf, 0, read); michael@0: read = fr.read(buf); michael@0: } michael@0: return sb.toString(); michael@0: } finally { michael@0: fr.close(); michael@0: } michael@0: } michael@0: michael@0: private void writeFile(File target, String data) throws IOException { michael@0: FileWriter writer = new FileWriter(target); michael@0: try { michael@0: writer.write(data); michael@0: } finally { michael@0: writer.close(); michael@0: } michael@0: } michael@0: }