1+ package at .tomtasche .reader .test ;
2+
3+ import static androidx .test .espresso .Espresso .onView ;
4+ import static androidx .test .espresso .action .ViewActions .clearText ;
5+ import static androidx .test .espresso .action .ViewActions .click ;
6+ import static androidx .test .espresso .action .ViewActions .typeText ;
7+ import static androidx .test .espresso .assertion .ViewAssertions .matches ;
8+ import static androidx .test .espresso .intent .matcher .IntentMatchers .hasAction ;
9+ import static androidx .test .espresso .matcher .ViewMatchers .isDisplayed ;
10+ import static androidx .test .espresso .matcher .ViewMatchers .withClassName ;
11+ import static androidx .test .espresso .matcher .ViewMatchers .withId ;
12+ import static androidx .test .espresso .matcher .ViewMatchers .withText ;
13+ import static org .hamcrest .Matchers .allOf ;
14+ import static org .hamcrest .Matchers .equalTo ;
15+
16+ import android .app .Activity ;
17+ import android .app .Instrumentation ;
18+ import android .content .Context ;
19+ import android .content .Intent ;
20+ import android .content .res .AssetManager ;
21+ import android .net .Uri ;
22+ import android .util .Log ;
23+
24+ import androidx .core .content .FileProvider ;
25+ import androidx .test .espresso .IdlingRegistry ;
26+ import androidx .test .espresso .IdlingResource ;
27+ import androidx .test .espresso .intent .Intents ;
28+ import androidx .test .ext .junit .runners .AndroidJUnit4 ;
29+ import androidx .test .filters .LargeTest ;
30+ import androidx .test .platform .app .InstrumentationRegistry ;
31+ import androidx .test .rule .ActivityTestRule ;
32+
33+ import org .junit .After ;
34+ import org .junit .Assert ;
35+ import org .junit .Before ;
36+ import org .junit .BeforeClass ;
37+ import org .junit .Rule ;
38+ import org .junit .Test ;
39+ import org .junit .runner .RunWith ;
40+
41+ import java .io .File ;
42+ import java .io .FileOutputStream ;
43+ import java .io .IOException ;
44+ import java .io .InputStream ;
45+ import java .io .OutputStream ;
46+
47+ import at .tomtasche .reader .R ;
48+ import at .tomtasche .reader .ui .activity .MainActivity ;
49+
50+ /**
51+ * Isolated test for password-protected documents to debug CI failures
52+ */
53+ @ LargeTest
54+ @ RunWith (AndroidJUnit4 .class )
55+ public class PasswordTestIsolated {
56+ private IdlingResource m_idlingResource ;
57+ private static File s_passwordTestFile ;
58+
59+ @ Rule
60+ public ActivityTestRule <MainActivity > mainActivityActivityTestRule = new ActivityTestRule <>(MainActivity .class );
61+
62+ @ BeforeClass
63+ public static void extractPasswordTestFile () throws IOException {
64+ Log .d ("PasswordTestIsolated" , "=== BeforeClass: Extracting password test file ===" );
65+
66+ Instrumentation instrumentation = InstrumentationRegistry .getInstrumentation ();
67+ Context appContext = instrumentation .getTargetContext ();
68+
69+ File appCacheDir = appContext .getCacheDir ();
70+ File testDocumentsDir = new File (appCacheDir , "test-documents-isolated" );
71+
72+ testDocumentsDir .mkdirs ();
73+ Assert .assertTrue ("Failed to create test directory" , testDocumentsDir .exists ());
74+
75+ AssetManager testAssetManager = instrumentation .getContext ().getAssets ();
76+
77+ s_passwordTestFile = new File (testDocumentsDir , "password-test.odt" );
78+
79+ try (InputStream inputStream = testAssetManager .open ("password-test.odt" );
80+ OutputStream out = new FileOutputStream (s_passwordTestFile )) {
81+ byte [] buf = new byte [1024 ];
82+ int len ;
83+ while ((len = inputStream .read (buf )) > 0 ) {
84+ out .write (buf , 0 , len );
85+ }
86+ }
87+
88+ Log .d ("PasswordTestIsolated" , "Password test file created at: " + s_passwordTestFile .getAbsolutePath ());
89+ Log .d ("PasswordTestIsolated" , "Password test file size: " + s_passwordTestFile .length ());
90+ Assert .assertEquals ("File size mismatch" , 12671L , s_passwordTestFile .length ());
91+ }
92+
93+ @ Before
94+ public void setUp () {
95+ Log .d ("PasswordTestIsolated" , "=== setUp: Initializing test ===" );
96+
97+ MainActivity mainActivity = mainActivityActivityTestRule .getActivity ();
98+ Assert .assertNotNull ("MainActivity is null in setUp" , mainActivity );
99+
100+ m_idlingResource = mainActivity .getOpenFileIdlingResource ();
101+ IdlingRegistry .getInstance ().register (m_idlingResource );
102+
103+ // Close system dialogs which may cover our Activity
104+ mainActivity .sendBroadcast (new Intent (Intent .ACTION_CLOSE_SYSTEM_DIALOGS ));
105+
106+ Intents .init ();
107+
108+ // Give the activity time to fully initialize
109+ try {
110+ Thread .sleep (2000 );
111+ } catch (InterruptedException e ) {
112+ Log .e ("PasswordTestIsolated" , "Sleep interrupted" , e );
113+ }
114+ }
115+
116+ @ After
117+ public void tearDown () {
118+ Log .d ("PasswordTestIsolated" , "=== tearDown: Cleaning up ===" );
119+
120+ Intents .release ();
121+
122+ if (null != m_idlingResource ) {
123+ IdlingRegistry .getInstance ().unregister (m_idlingResource );
124+ }
125+
126+ mainActivityActivityTestRule .getActivity ().finish ();
127+ }
128+
129+ @ Test
130+ public void testPasswordProtectedDocument () {
131+ Log .d ("PasswordTestIsolated" , "=== Starting password test ===" );
132+
133+ Assert .assertNotNull ("Password test file is null" , s_passwordTestFile );
134+ Assert .assertTrue ("Password test file doesn't exist" , s_passwordTestFile .exists ());
135+
136+ Context appCtx = InstrumentationRegistry .getInstrumentation ().getTargetContext ();
137+ Uri testFileUri = FileProvider .getUriForFile (appCtx , appCtx .getPackageName () + ".provider" , s_passwordTestFile );
138+
139+ Intents .intending (hasAction (Intent .ACTION_OPEN_DOCUMENT )).respondWith (
140+ new Instrumentation .ActivityResult (Activity .RESULT_OK ,
141+ new Intent ()
142+ .setData (testFileUri )
143+ .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION )
144+ )
145+ );
146+
147+ // Verify activity state before interaction
148+ MainActivity activity = mainActivityActivityTestRule .getActivity ();
149+ Assert .assertNotNull ("Activity is null before test" , activity );
150+ Assert .assertFalse ("Activity is finishing before test" , activity .isFinishing ());
151+ Assert .assertFalse ("Activity is destroyed before test" , activity .isDestroyed ());
152+
153+ Log .d ("PasswordTestIsolated" , "Opening document menu" );
154+ onView (allOf (withId (R .id .menu_open ), withText ("Open" )))
155+ .check (matches (isDisplayed ()))
156+ .perform (click ());
157+
158+ Log .d ("PasswordTestIsolated" , "Waiting for password dialog" );
159+
160+ // Wait with timeout for password dialog
161+ try {
162+ onView (withText ("This document is password-protected" ))
163+ .check (matches (isDisplayed ()));
164+ Log .d ("PasswordTestIsolated" , "Password dialog appeared" );
165+ } catch (Exception e ) {
166+ Log .e ("PasswordTestIsolated" , "Failed to find password dialog" , e );
167+ // Check if activity crashed
168+ Assert .assertFalse ("Activity was destroyed while waiting for dialog" ,
169+ mainActivityActivityTestRule .getActivity ().isDestroyed ());
170+ throw e ;
171+ }
172+
173+ // Enter correct password
174+ Log .d ("PasswordTestIsolated" , "Entering password" );
175+ onView (withClassName (equalTo ("android.widget.EditText" )))
176+ .perform (clearText (), typeText ("passwort" ));
177+
178+ onView (withId (android .R .id .button1 ))
179+ .perform (click ());
180+
181+ Log .d ("PasswordTestIsolated" , "Test completed successfully" );
182+ }
183+ }
0 commit comments