|
1 package org.mozilla.gecko.tests; |
|
2 |
|
3 import java.io.File; |
|
4 import java.io.FileReader; |
|
5 import java.io.FileWriter; |
|
6 import java.io.IOException; |
|
7 |
|
8 import org.json.JSONArray; |
|
9 import org.json.JSONException; |
|
10 import org.json.JSONObject; |
|
11 import org.mozilla.gecko.Actions; |
|
12 import org.mozilla.gecko.Assert; |
|
13 import org.mozilla.gecko.FennecMochitestAssert; |
|
14 |
|
15 public abstract class SessionTest extends BaseTest { |
|
16 protected Navigation mNavigation; |
|
17 |
|
18 @Override |
|
19 public void setUp() throws Exception { |
|
20 super.setUp(); |
|
21 |
|
22 mNavigation = new Navigation(mDevice); |
|
23 } |
|
24 |
|
25 /** |
|
26 * A generic session object representing a collection of items that has a |
|
27 * selected index. |
|
28 */ |
|
29 protected abstract class SessionObject<T> { |
|
30 private final int mIndex; |
|
31 private final T[] mItems; |
|
32 |
|
33 public SessionObject(int index, T... items) { |
|
34 mIndex = index; |
|
35 mItems = items; |
|
36 } |
|
37 |
|
38 public int getIndex() { |
|
39 return mIndex; |
|
40 } |
|
41 |
|
42 public T[] getItems() { |
|
43 return mItems; |
|
44 } |
|
45 } |
|
46 |
|
47 protected class PageInfo { |
|
48 private String url; |
|
49 private String title; |
|
50 |
|
51 public PageInfo(String key) { |
|
52 if (key.startsWith("about:")) { |
|
53 url = key; |
|
54 } else { |
|
55 url = getPage(key); |
|
56 } |
|
57 title = key; |
|
58 } |
|
59 } |
|
60 |
|
61 protected class SessionTab extends SessionObject<PageInfo> { |
|
62 public SessionTab(int index, PageInfo... items) { |
|
63 super(index, items); |
|
64 } |
|
65 } |
|
66 |
|
67 protected class Session extends SessionObject<SessionTab> { |
|
68 public Session(int index, SessionTab... items) { |
|
69 super(index, items); |
|
70 } |
|
71 } |
|
72 |
|
73 /** |
|
74 * Walker for visiting items in a browser-like navigation order. |
|
75 */ |
|
76 protected abstract class NavigationWalker<T> { |
|
77 private final T[] mItems; |
|
78 private final int mIndex; |
|
79 |
|
80 public NavigationWalker(SessionObject<T> obj) { |
|
81 mItems = obj.getItems(); |
|
82 mIndex = obj.getIndex(); |
|
83 } |
|
84 |
|
85 /** |
|
86 * Walks over the list of items, calling the onItem() callback for each. |
|
87 * |
|
88 * The selected item is the first item visited. Each item after the |
|
89 * selected item is then visited in ascending index order. Finally, the |
|
90 * list is iterated in reverse, and each item before the selected item |
|
91 * is visited in descending index order. |
|
92 */ |
|
93 public void walk() { |
|
94 onItem(mItems[mIndex], mIndex); |
|
95 for (int i = mIndex + 1; i < mItems.length; i++) { |
|
96 goForward(); |
|
97 onItem(mItems[i], i); |
|
98 } |
|
99 if (mIndex > 0) { |
|
100 for (int i = mItems.length - 2; i >= 0; i--) { |
|
101 goBack(); |
|
102 if (i < mIndex) { |
|
103 onItem(mItems[i], i); |
|
104 } |
|
105 } |
|
106 } |
|
107 } |
|
108 |
|
109 /** |
|
110 * Callback when an item is visited during a walk. |
|
111 * |
|
112 * Only one callback is executed per item. |
|
113 */ |
|
114 public abstract void onItem(T item, int currentIndex); |
|
115 |
|
116 /** |
|
117 * Callback executed for each back step of the walk. |
|
118 */ |
|
119 public void goBack() {} |
|
120 |
|
121 /** |
|
122 * Callback executed for each forward step of the walk. |
|
123 */ |
|
124 public void goForward() {} |
|
125 } |
|
126 |
|
127 /** |
|
128 * Loads a set of tabs in the browser specified by the given session. |
|
129 * |
|
130 * @param session Session to load |
|
131 */ |
|
132 protected void loadSessionTabs(Session session) { |
|
133 // Verify initial about:home tab |
|
134 verifyTabCount(1); |
|
135 verifyUrl("about:home"); |
|
136 |
|
137 SessionTab[] tabs = session.getItems(); |
|
138 for (int i = 0; i < tabs.length; i++) { |
|
139 final SessionTab tab = tabs[i]; |
|
140 final PageInfo[] pages = tab.getItems(); |
|
141 |
|
142 // New tabs always start with about:home, so make sure about:home |
|
143 // is always the first entry. |
|
144 mAsserter.is(pages[0].url, "about:home", "first page in tab is about:home"); |
|
145 |
|
146 // If this is the first tab, the tab already exists, so no need to |
|
147 // create a new one. Otherwise, create a new tab if we're loading |
|
148 // the first the first page in the set. |
|
149 if (i > 0) { |
|
150 addTab(); |
|
151 } |
|
152 |
|
153 for (int j = 1; j < pages.length; j++) { |
|
154 Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); |
|
155 |
|
156 loadUrl(pages[j].url); |
|
157 |
|
158 pageShowExpecter.blockForEvent(); |
|
159 pageShowExpecter.unregisterListener(); |
|
160 } |
|
161 |
|
162 final int index = tab.getIndex(); |
|
163 for (int j = pages.length - 1; j > index; j--) { |
|
164 mNavigation.back(); |
|
165 } |
|
166 } |
|
167 |
|
168 selectTabAt(session.getIndex()); |
|
169 } |
|
170 |
|
171 /** |
|
172 * Verifies that the set of open tabs matches the given session. |
|
173 * |
|
174 * @param session Session to verify |
|
175 */ |
|
176 protected void verifySessionTabs(Session session) { |
|
177 verifyTabCount(session.getItems().length); |
|
178 |
|
179 (new NavigationWalker<SessionTab>(session) { |
|
180 boolean mFirstTabVisited; |
|
181 |
|
182 @Override |
|
183 public void onItem(SessionTab tab, int currentIndex) { |
|
184 // The first tab to check should already be selected at startup |
|
185 if (mFirstTabVisited) { |
|
186 selectTabAt(currentIndex); |
|
187 } else { |
|
188 mFirstTabVisited = true; |
|
189 } |
|
190 |
|
191 (new NavigationWalker<PageInfo>(tab) { |
|
192 @Override |
|
193 public void onItem(PageInfo page, int currentIndex) { |
|
194 if (page.url.equals("about:home")) { |
|
195 waitForText("Enter Search or Address"); |
|
196 verifyUrl(page.url); |
|
197 } else { |
|
198 waitForText(page.title); |
|
199 verifyPageTitle(page.title); |
|
200 } |
|
201 } |
|
202 |
|
203 @Override |
|
204 public void goBack() { |
|
205 mNavigation.back(); |
|
206 } |
|
207 |
|
208 @Override |
|
209 public void goForward() { |
|
210 mNavigation.forward(); |
|
211 } |
|
212 }).walk(); |
|
213 } |
|
214 }).walk(); |
|
215 } |
|
216 |
|
217 /** |
|
218 * Gets session restore JSON corresponding to the open session. |
|
219 * |
|
220 * The JSON format follows the format used in Gecko for session restore and |
|
221 * should be interchangeable with the Gecko's generated sessionstore.js. |
|
222 * |
|
223 * @param session Session to serialize |
|
224 * @return JSON string of session |
|
225 */ |
|
226 protected String buildSessionJSON(Session session) { |
|
227 final SessionTab[] sessionTabs = session.getItems(); |
|
228 String sessionString = null; |
|
229 |
|
230 try { |
|
231 final JSONArray tabs = new JSONArray(); |
|
232 |
|
233 for (int i = 0; i < sessionTabs.length; i++) { |
|
234 final JSONObject tab = new JSONObject(); |
|
235 final JSONArray entries = new JSONArray(); |
|
236 final SessionTab sessionTab = sessionTabs[i]; |
|
237 final PageInfo[] pages = sessionTab.getItems(); |
|
238 |
|
239 for (int j = 0; j < pages.length; j++) { |
|
240 final PageInfo page = pages[j]; |
|
241 final JSONObject entry = new JSONObject(); |
|
242 entry.put("url", page.url); |
|
243 entry.put("title", page.title); |
|
244 entries.put(entry); |
|
245 } |
|
246 |
|
247 tab.put("entries", entries); |
|
248 tab.put("index", sessionTab.getIndex() + 1); |
|
249 tabs.put(tab); |
|
250 } |
|
251 |
|
252 JSONObject window = new JSONObject(); |
|
253 window.put("tabs", tabs); |
|
254 window.put("selected", session.getIndex() + 1); |
|
255 sessionString = new JSONObject().put("windows", new JSONArray().put(window)).toString(); |
|
256 } catch (JSONException e) { |
|
257 mAsserter.ok(false, "JSON exception", getStackTraceString(e)); |
|
258 } |
|
259 |
|
260 return sessionString; |
|
261 } |
|
262 |
|
263 /** |
|
264 * @see SessionTest#verifySessionJSON(Session, String, Assert) |
|
265 */ |
|
266 protected void verifySessionJSON(Session session, String sessionString) { |
|
267 verifySessionJSON(session, sessionString, mAsserter); |
|
268 } |
|
269 |
|
270 /** |
|
271 * Verifies a session JSON string against the given session. |
|
272 * |
|
273 * @param session Session to verify against |
|
274 * @param sessionString JSON string to verify |
|
275 * @param asserter Assert class to use during verification |
|
276 */ |
|
277 protected void verifySessionJSON(Session session, String sessionString, Assert asserter) { |
|
278 final SessionTab[] sessionTabs = session.getItems(); |
|
279 |
|
280 try { |
|
281 final JSONObject window = new JSONObject(sessionString).getJSONArray("windows").getJSONObject(0); |
|
282 final JSONArray tabs = window.getJSONArray("tabs"); |
|
283 final int optSelected = window.optInt("selected", -1); |
|
284 |
|
285 asserter.is(optSelected, session.getIndex() + 1, "selected tab matches"); |
|
286 |
|
287 for (int i = 0; i < tabs.length(); i++) { |
|
288 final JSONObject tab = tabs.getJSONObject(i); |
|
289 final int index = tab.getInt("index"); |
|
290 final JSONArray entries = tab.getJSONArray("entries"); |
|
291 final SessionTab sessionTab = sessionTabs[i]; |
|
292 final PageInfo[] pages = sessionTab.getItems(); |
|
293 |
|
294 asserter.is(index, sessionTab.getIndex() + 1, "selected page index matches"); |
|
295 |
|
296 for (int j = 0; j < entries.length(); j++) { |
|
297 final JSONObject entry = entries.getJSONObject(j); |
|
298 final String url = entry.getString("url"); |
|
299 final String title = entry.optString("title"); |
|
300 final PageInfo page = pages[j]; |
|
301 |
|
302 asserter.is(url, page.url, "URL in JSON matches session URL"); |
|
303 if (!page.url.startsWith("about:")) { |
|
304 asserter.is(title, page.title, "title in JSON matches session title"); |
|
305 } |
|
306 } |
|
307 } |
|
308 } catch (JSONException e) { |
|
309 asserter.ok(false, "JSON exception", getStackTraceString(e)); |
|
310 } |
|
311 } |
|
312 |
|
313 /** |
|
314 * Exception thrown by NonFatalAsserter for assertion failures. |
|
315 */ |
|
316 public static class AssertException extends RuntimeException { |
|
317 public AssertException(String msg) { |
|
318 super(msg); |
|
319 } |
|
320 } |
|
321 |
|
322 /** |
|
323 * Asserter that throws an AssertException on failure instead of aborting |
|
324 * the test. |
|
325 * |
|
326 * This can be used in methods called via waitForCondition() where an assertion |
|
327 * might not immediately succeed. |
|
328 */ |
|
329 public class NonFatalAsserter extends FennecMochitestAssert { |
|
330 @Override |
|
331 public void ok(boolean condition, String name, String diag) { |
|
332 if (!condition) { |
|
333 String details = (diag == null ? "" : " | " + diag); |
|
334 throw new AssertException("Assertion failed: " + name + details); |
|
335 } |
|
336 mAsserter.ok(condition, name, diag); |
|
337 } |
|
338 } |
|
339 |
|
340 /** |
|
341 * Gets a URL for a dynamically-generated page. |
|
342 * |
|
343 * The page will have a URL unique to the given ID, and the page's title |
|
344 * will match the given ID. |
|
345 * |
|
346 * @param id ID used to generate page URL |
|
347 * @return URL of the page |
|
348 */ |
|
349 protected String getPage(String id) { |
|
350 return getAbsoluteUrl("/robocop/robocop_dynamic.sjs?id=" + id); |
|
351 } |
|
352 |
|
353 protected String readProfileFile(String filename) { |
|
354 try { |
|
355 return readFile(new File(mProfile, filename)); |
|
356 } catch (IOException e) { |
|
357 mAsserter.ok(false, "Error reading" + filename, getStackTraceString(e)); |
|
358 } |
|
359 return null; |
|
360 } |
|
361 |
|
362 protected void writeProfileFile(String filename, String data) { |
|
363 try { |
|
364 writeFile(new File(mProfile, filename), data); |
|
365 } catch (IOException e) { |
|
366 mAsserter.ok(false, "Error writing to " + filename, getStackTraceString(e)); |
|
367 } |
|
368 } |
|
369 |
|
370 private String readFile(File target) throws IOException { |
|
371 if (!target.exists()) { |
|
372 return null; |
|
373 } |
|
374 |
|
375 FileReader fr = new FileReader(target); |
|
376 try { |
|
377 StringBuffer sb = new StringBuffer(); |
|
378 char[] buf = new char[8192]; |
|
379 int read = fr.read(buf); |
|
380 while (read >= 0) { |
|
381 sb.append(buf, 0, read); |
|
382 read = fr.read(buf); |
|
383 } |
|
384 return sb.toString(); |
|
385 } finally { |
|
386 fr.close(); |
|
387 } |
|
388 } |
|
389 |
|
390 private void writeFile(File target, String data) throws IOException { |
|
391 FileWriter writer = new FileWriter(target); |
|
392 try { |
|
393 writer.write(data); |
|
394 } finally { |
|
395 writer.close(); |
|
396 } |
|
397 } |
|
398 } |