Repository: psychopy/psychopy

Issue:
PsychoJs files created within index.html folder instead of next to psyexp causing 403 forbidden error (when using run the study online button on new project)
PsychoPy Builder sometimes creates the js (and html) files in a folder called "index.html" which then causes a "403 Forbidden" error message on Pavlovia because the files are not in the expected location (next to the psyexp file).

I think this bug already exists longer (maybe since 2022.2.0), but before I was not able to easily reproduce it. Today, I noticed that it always occurs if one creates a new projects and instantly runs it with the "run the study online" button.

I also recorded a video showing the issue:
https://user-images.githubusercontent.com/5979916/203097947-e2feb648-9a08-47a7-bb68-4f378c2918da.mp4

Important note:
If one does the very same as in the video, but first hits the "sync with web project" button and thereafter the "run the study online" button, it works as expected (js and html files are created next to the psyexp file).


Discussion:
Good catch! I think I see what's causing this - both the Pavlovia Run and Pavlovia Sync buttons call `self.fileExport` (the method which makes the js and html files), but while Pavlovia Sync uses `self._getHtmlPath(self.filename)`, Pavlovia Run uses `Path(self.project.localRoot) / 'index.html'` - which is incorrectly interpreted as a folder and that's how the `index.html` folder is created.

The easy answer is just to use the same values, but it may be better to actually call the Pavlovia Sync method when Pavlovia Run is clicked, to avoid any other discrepancies. Will look into it and hopefully get this fixed quickly!

Resolving patch:
diff --git a/psychopy/app/builder/builder.py b/psychopy/app/builder/builder.py
--- a/psychopy/app/builder/builder.py
+++ b/psychopy/app/builder/builder.py
@@ -1390,33 +1390,28 @@ def openPluginManager(self, evt=None):
 
     def onPavloviaSync(self, evt=None):
         if Path(self.filename).is_file():
+            # Save file
             self.fileSave(self.filename)
+            # If allowed by prefs, export html and js files
             if self._getExportPref('on sync'):
                 htmlPath = self._getHtmlPath(self.filename)
                 if htmlPath:
                     self.fileExport(htmlPath=htmlPath)
                 else:
                     return
-
+        # Disable button
         self.enablePavloviaButton(['pavloviaSync', 'pavloviaRun'], False)
+        # Attempy sync, re-enable buttons if it fails
         try:
             pavlovia_ui.syncProject(parent=self, file=self.filename, project=self.project)
         finally:
             self.enablePavloviaButton(['pavloviaSync', 'pavloviaRun'], True)
 
     def onPavloviaRun(self, evt=None):
-        if self._getExportPref('on save') or self._getExportPref('on sync'):
-            # If export on save/sync, sync now
-            pavlovia_ui.syncProject(parent=self, project=self.project)
-        elif self._getExportPref('manually'):
-            # If set to manual, only sync if needed to create a project to run
-            if self.project is None:
-                pavlovia_ui.syncProject(parent=self, project=self.project)
+        # Sync project
+        self.onPavloviaSync()
 
         if self.project is not None:
-            # Make sure we have a html file to run
-            if not (Path(self.project.localRoot) / 'index.html').is_file():
-                self.fileExport(htmlPath=Path(self.project.localRoot) / 'index.html')
             # Update project status
             self.project.pavloviaStatus = 'ACTIVATED'
             # Run


Test patch: